waltcow / blog Goto Github PK
View Code? Open in Web Editor NEWA personal blog
A personal blog
Classes, structures, and enumerations can define subscripts, which are shortcuts for accessing the member elements of a collection. For example, you access elements in an Array instance as someArray[index] and elements in a Dictionary instance as someDictionary[key].
You can define multiple subscripts for a single type, and the appropriate subscript overload to use is selected based on the type of index value you pass to the subscript. Subscripts are not limited to a single dimension, and you can define subscripts with multiple input parameters to suit your custom type’s needs.
subscript(index: Int) -> Int {
get {
// return an appropriate subscript value here
}
set(newValue) {
// perform a suitable setting action here
}
}
//read-only computed properties
subscript(index: Int) -> Int {
// return an appropriate subscript value here
}
resolve.root
, resolve.fallback
, resolve.modulesDirectories
上述三个选项将被合并为一个标准配置项:resolve.modules. 更多关于resolve的信息信息可查阅 resolving.
resolve: {
- root: path.join(__dirname, "src")
+ modules: [
+ path.join(__dirname, "src"),
+ "node_modules"
+ ]
}
resolve.extensions
该配置项将不再要求强制转入一个空字符串,而被改动到了resolve.enforceExtension下, 更多关于resolve的信息信息可查阅 resolving.
resolve.*
更多相关改动和一些不常用的配置项在此不一一列举,大家如果在实际项目中用到可以到resolving中进行查看.
module.loaders
将变为 module.rules
旧版本中loaders配置项将被功能更为强大的rules取代,同时考虑到新旧版本的兼容,之前旧版本的module.loaders
的相关写法仍旧有效,loaders中的相关配置项也依旧可以被识别。
新的loader配置规则会变得更加通俗易用,因此官方也非常推荐用户能及时按module.rules
中的相关配置进行调整升级。
module: {
- loaders: [
+ rules: [
{
test: /\.css$/,
- loaders: [
+ use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
- query: {
+ options: {
modules: true
}
}
]
},
{
test: /\.jsx$/,
loader: "babel-loader", // Do not use "use" here
options: {
// ...
}
}
]
}
同webpack1.X中类似,loaders继续支持链式写法,可将相关正则匹配到的文件资源数据在几个loader之间进行共享传递,详细使用说明可见 rule.use。
在wepback2中,用户可通过use
项来指定需要用到的loaders列表(官方推荐),而在weback1中,如果需要配置多个loaders则需要依靠简单的 !
符来切分,这种语法出于新旧兼容的考虑,只会在module.loaders
中生效。
module: {
- loaders: {
+ rules: {
test: /\.less$/,
- loader: "style-loader!css-loader!less-loader"
+ use: [
+ "style-loader",
+ "css-loader",
+ "less-loader"
+ ]
}
}
##module
名称后自动自动补全 -loader
的功能将被移除
在配置loader时,官方不再允许省略-loader
扩展名,loader的配置写法上将逐步趋于严谨。
module: {
rules: [
{
use: [
- "style",
+ "style-loader",
- "css",
+ "css-loader",
- "less",
+ "less-loader",
]
}
]
}
当然,如果你想继续保持之前的省略写法,你写可以在resolveLoader.moduleExtensions
中开启默认扩展名配置,不过这种做法并不被推荐。
+ resolveLoader: {
+ moduleExtensions: ["-loader"]
+ }
可以从这里查看 #2986此次变更的原因;
json-loader
无需要独立安装当我们需要读取json格式文件时,我们不再需要安装任何loader,webpack2中将会内置 json-loader,自动支持json格式的读取(喜大普奔啊)。
module: {
rules: [
- {
- test: /\.json/,
- loader: "json-loader"
- }
]
}
为何需要默认支持json格式官方的解释是为了在webpack, node.js and browserify三种构建环境下提供无差异的开发体验。
loader
配置项将默认从context
中读取在webpack 1中的一些特殊的loader在读取对应资源时,需要通过require.resolve
指定后才能指定生效。从webpack 2后,配置loader在直接从context
中进行读取,这就解决了一些在使用“npm链接”或引用模块之外的context
造成的模块重复导入的问题。
配置中可以删除如下代码:
module: {
rules: [
{
// ...
- loader: require.resolve("my-loader")
+ loader: "my-loader"
}
]
},
resolveLoader: {
- root: path.resolve(__dirname, "node_modules")
}
module.preLoaders
和 module.postLoaders
将被移除 module: {
- preLoaders: [
+ rules: [
{
test: /\.js$/,
+ enforce: "pre",
loader: "eslint-loader"
}
]
}
之前需要用到preLoader的地方可以改到rules的enfore中进行配置。
UglifyJsPlugin
中的 sourceMap
配置项将默认关闭UglifyJsPlugin
中的sourceMap
默认项将从 true
变为 false
。
这就意味着当你的js编译压缩后,需要继续读取原始脚本信息的行数,位置,警告等有效调试信息时,你需要手动开启UglifyJsPlugin
的配置项:sourceMap: true
。
devtool: "source-map",
plugins: [
new UglifyJsPlugin({
+ sourceMap: true
})
]
UglifyJsPlugin
的警告配置将默认关闭UglifyJsPlugin
中的 compress.warnings
默认项将从 true
变为 false
。
这就意味着当你想在编译压缩的时候查看一部分js的警告信息时,你需要将compress.warnings
手动设置为 true
。
devtool: "source-map",
plugins: [
new UglifyJsPlugin({
+ compress: {
+ warnings: true
+ }
})
]
UglifyJsPlugin
不再支持让 Loaders
最小化文件的模式了UglifyJsPlugin
将不再支持让 Loaders 最小化文件的模式。debug
选项已经被移除。Loaders 不能从 webpack 的配置中读取到他们的配置项。
loade的最小化文件模式将会在webpack 3或者后续版本中被彻底取消掉.
为了兼容部分旧式loader,你可以通过 LoaderOptionsPlugin
的配置项来提供这些功能。
plugins: [
+ new webpack.LoaderOptionsPlugin({
+ minimize: true
+ })
]
DedupePlugin
已经被移除webpack.optimize.DedupePlugin
不再需要. 从你以前的配置移除这个配置选项.
BannerPlugin
配置项将有所改变BannerPlugin
将不再允许接受两个参数,而是只提供一个对象配置项.
plugins: [
- new webpack.BannerPlugin('Banner', {raw: true, entryOnly: true});
+ new webpack.BannerPlugin({banner: 'Banner', raw: true, entryOnly: true});
]
OccurrenceOrderPlugin
将被内置加入不需要再针对OccurrenceOrderPlugin
进行配置
plugins: [
- new webpack.optimize.OccurrenceOrderPlugin()
]
ExtractTextWebpackPlugin
配置项将有所改变ExtractTextPlugin ] 1.0.0 在webpack v2将无法使用,你需要重新指定安装ExtractTextPlugin
的webpack2的适配版本.
npm install --save-dev extract-text-webpack-plugin@beta
更新后的ExtractTextPlugin
版本会针对wepback2进行相应的调整。
ExtractTextPlugin.extract
的配置书写方式将调整module: {
rules: [
test: /.css$/,
- loader: ExtractTextPlugin.extract("style-loader", "css-loader", { publicPath: "/dist" })
+ loader: ExtractTextPlugin.extract({
+ fallbackLoader: "style-loader",
+ loader: "css-loader",
+ publicPath: "/dist"
+ })
]
}
new ExtractTextPlugin({options})
的配置书写方式将调整plugins: [
- new ExtractTextPlugin("bundle.css", { allChunks: true, disable: false })
+ new ExtractTextPlugin({
+ filename: "bundle.css",
+ disable: false,
+ allChunks: true
+ })
]
只有使用一个表达式的资源依赖引用(i. e. require(expr)
),现在将创建一个空的context,而不是一个context的完整目录。
当在es2015的模块化中无法工作时,请最好重构这部分的代码,如果无法进行修改这部分代码,你可以在ContextReplacementPlugin
中来提示编译器做出正确处理。
###Cli使用自定义参数作为配置项传入方式将做调整
如果你随意将自定义参数通过cli传入到配置项中,如:
webpack --custom-stuff
// webpack.config.js
var customStuff = process.argv.indexOf("--custom-stuff") >= 0;
/* ... */
module.exports = config;
你会发现这将不会被允许,cli的执行将会遵循更为严格的标准。
取而代之的是用一个接口来做传递参数配置。这应该是新的代替方案,未来的工具开发也可能依赖于此。
webpack --env.customStuff
module.exports = function(env) {
var customStuff = env.customStuff;
/* ... */
return config;
};
查看更多介绍 CLI.
##require.ensure
和 AMD require
将采用异步式调用
require.ensure
和amd require
将默认采用异步的加载方式来调用,而非之前的当模块请求加载完成后再在回调函数中同步触发。
require.ensure
将基于原生的Promise
对象重新实现,当你在使用 require.ensure
时请确保你的运行环境默认支持Promise对象,如果缺少则推荐使用安装polyfill.
options
来设置在webpack.config.js
中将不再允许使用自定义属性来配置loder,这直接带来的一个影响是:在ts
配置项中的自定义属性将无法在被在webpack2中正确使用:
module.exports = {
...
module: {
rules: [{
test: /\.tsx?$/,
loader: 'ts-loader'
}]
},
// does not work with webpack 2
ts: { transpileOnly: false }
}
options
?这是一个非常好的提问,严格意义上来说,custom property
和options
均是用于webpack loader的配置方式,从更通俗的说法上看,options
应该被称作query
,作为一种类似字符串的形式被追加到每一个loader的命名后面,非常类似我们用于url中的查询字符串,但在实际应用中功能要更为强大:
module.exports = {
...
module: {
rules: [{
test: /\.tsx?$/,
loader: 'ts-loader?' + JSON.stringify({ transpileOnly: false })
}]
}
}
options也可作为一个独立的字面对象量,在loader的配置中搭配使用。
module.exports = {
...
module: {
rules: [{
test: /\.tsx?$/,
loader: 'ts-loader',
options: { transpileOnly: false }
}]
}
}
LoaderOptionsPlugin
context部分loader需要配置context
信息, 并且支持从配置文件中读取。这需要loader通过用长选项传递进来,更多loader
的明细配置项可以查阅相关文档。
为了兼容部分旧式的loader配置,也可以采用如下插件的形式来进行配置:
plugins: [
+ new webpack.LoaderOptionsPlugin({
+ options: {
+ context: __dirname
+ }
+ })
]
debug
debug
作为loader中的一个调试模式选项,可以在webpack1的配置中灵活切换。在webpack2中,则需要loader
通过用长选项传递进来,更多loader的明细配置项可以查阅相关文档。
loder的debug
模式在webpack3.0或者后续版本中将会被移除。
为了兼容部分旧式的loader配置,也可以采用如下插件的形式来进行配置:
- debug: true,
plugins: [
+ new webpack.LoaderOptionsPlugin({
+ debug: true
+ })
]
在webpack1中,你需要使用require.ensure
实现chunks
的懒加载,如:
require.ensure([], function(require) {
var foo = require("./module");
});
在es2015的 loader中通过定义import()
作为资源加载方法,当读取到符合ES2015规范的模块时,可实现模块中的内容在运行时动态加载。
webpack在处理import()
时可以实现按需提取开发中所用到的模块资源,再写入到各个独立的chunk中。webpack2已经支持原生的 ES6 的模块加载器了,这意味着 webpack 2 能够理解和处理 import
和export
了。
import()
支持将模块名作为参数出入并且返回一个Promise
对象。
function onClick() {
import("./module").then(module => {
return module.default;
}).catch(err => {
console.log("Chunk loading failed");
});
}
这样做的还有一个额外的好处就是当我们的模块加载失败时也可以被捕获到了,因为这些都会遵循Promise
的标准来实现。
值得注意的地方:require.ensure
的第三个参数选项允许使用简单的chunk命名方式,但是import
API中将不被支持,如果你希望继续采用函数式的写法,你可以继续使用require.ensure
。
require.ensure([], function(require) {
var foo = require("./module");
}, "custom-chunk-name");
(注: System.import
将会被弃用,webpack中将不再推荐使用 System.import
,官方也推荐使用import
进行替换,详见 v2.1.0-beta.28)
如果想要继续使用Babel中提供的import
,你需要独立安装 dynamic-import 插件并且选择babel的Stage 3
来捕获时的错误, 当然这也可以根据实际情况来操作而不做强制约束。
现在import()
中的传参可支持部分表达式的写法了,如果之前有接触过CommonJS中require()
表达式写法,应该不会对此感到陌生,(它的操作其实和 CommonJS 是类似的,给所有可能的文件创建一个环境,当你传递那部分代码的模块还不确定的时候,webpack 会自动生成所有可能的模块,然后根据需求加载。这个特性在前端路由的时候很有用,可以实现按需加载资源)
import()
会针对每一个读取到的module创建独立的separte chunk
。
function route(path, query) {
return import(`./routes/${path}/route`)
.then(route => new route.Route(query));
}
// This creates a separate chunk for each possible route
在 AMD 和 CommonJS 模块加载器中,你可以混合使用所有(三种)的模块类型(即使是在同一个文件里面)。
// CommonJS consuming ES2015 Module
var book = require("./book");
book.currentPage;
book.readPage();
book.default === "This is a book";
// ES2015 Module consuming CommonJS
import fs from "fs"; // module.exports map to default
import { readFileSync } from "fs"; // named exports are read from returned object+
typeof fs.readFileSync === "function";
typeof readFileSync === "function";
注:es2015 balel
的默认预处理会把 ES6 模块加载器转化成 CommonJS 模块加载。要是想使用 webpack 新增的对原生 ES6 模块加载器的支持,你需要使用 es2015-webpack
来代替,另外如果你希望继续使用babel,则需要通过配置babel项,使其不会强制解析这部分的module symbols以便webpack能正确使用它们,babel的配置如下:
.babelrc
{
"presets": [
["es2015", { "modules": false }]
]
}
No need to change something, but opportunities
webpack中的资源参数已经开始支持模板字符串了,这意味着你可以使用如下的配置写法:
- require("./templates/" + name);
+ require(`./templates/${name}`);
###配置支持项支持Promise
webpack现在在配置文件项中返回Promise
了,这就允许你在配置中可以进行一些异步的写法了,如下所示:
webpack.config.js
module.exports = function() {
return fetchLangs().then(lang => ({
entry: "...",
// ...
plugins: [
new DefinePlugin({ LANGUAGE: lang })
]
}));
};
webpack中的loader配置支持如下写法:
module: {
rules: [
{
resource: /filename/, // matches "/path/filename.js"
resourceQuery: /querystring/, // matches "/filename.js?querystring"
issuer: /filename/, // matches "/path/something.js" if requested from "/path/filename.js"
}
]
}
如下有更多的CLI 参数项可用:
--define process.env.NODE_ENV="production"
支持直接配置DefinePlugin
.
--display-depth
能显示每个entry中的module的资源深度
--display-used-exports
能显示每个module中依赖使用了哪些资源.
--display-max-modules
能限制显示output中引用到的资源数量 (默认显示15个).
-p 指定当前的编译环境为生产环境,即修改:process.env.NODE_ENV
为 "production"
Loaders现在将默认开启资源缓存了,如果你不希望loader读缓存则需要在配置中指明:
// Cacheable loader
module.exports = function(source) {
- this.cacheable();
return source;
}
// Not cacheable loader
module.exports = function(source) {
+ this.cacheable(false);
return source;
}
webpack1中的loader参数项中只支持JSON.stringify-able
这种json字符串的写法;
webpack2中的loader参数项中已经可以支持任意的JS对象的写法了。
使用复合选项时会有一个限制,你需要配置一个ident
作为项来保证能正确引用到其他的loader,这意味着通过配置我们可以在内联写法中去调用对应依赖的加载器,如下:
require("some-loader??by-ident!resource")
{
test: /.../,
loader: "...",
options: {
ident: "by-ident",
magic: () => return Math.random()
}
}
这种写法在平常开发中用的不算多,但是有一种场景下会比较有用,就是当我们的loader需要去生成独立的代码片段时,如,我们在使用style-loader
生成一个模块时,需要依赖前面的loader计算的结果。
// style-loader generated code (simplified)
var addStyle = require("./add-style");
var css = require("-!css-loader?{"modules":true}!postcss-loader??postcss-ident");
addStyle(css);
在这种复杂选项的使用时ident
就有用武之地了。
webpack2无论是从优化资源配置项,到向es6 module,Promise等新标准接轨,再到编译环境和性能的优化,再到API设计的整体规范性上,相对V1的改进还是非常显著的,希望大家多多尝试,及时反馈交流,让webapck的生态圈变得日益活跃强大。
Spring Framework provides an abstraction layer with set of annotations for caching support and can work together with various cache implementation like Redis, EhCache, Hazelcast, Infinispan and many more.
Fulfill cache after method execution, next invocation with the same arguments will be omitted and result will be loaded from cache. Annotation provide useful feature called conditional caching. In some case no all data should be cached e.g. you want store in memory only most popular posts.
@Cacheable(value = "post-single", key = "#id", unless = "#result.shares < 500")
@GetMapping("/{id}")
public Post getPostByID(@PathVariable String id) throws PostNotFoundException {
log.info("get post with id {}", id);
return postService.getPostByID(id);
}
@Cacheable(value = "post-top")
@GetMapping("/top")
public List<Post> getTopPosts() {
return postService.getTopPosts();
}
Annotation allows to update entry in cache and support same options like Cacheable annotation. Code below updates post and return it for cache provider to change entry with new value.
@CachePut(value = "post-single", key = "#post.id")
@PutMapping("/update")
public Post updatePostByID(@RequestBody Post post) throws PostNotFoundException {
log.info("update post with id {}", post.getId());
postService.updatePost(post);
return post;
}
Remove entry from cache, can be both conditional and global to all entries in specific cache.
@CacheEvict(value = "post-single", key = "#id")
@DeleteMapping("/delete/{id}")
public void deletePostByID(@PathVariable String id) throws PostNotFoundException {
log.info("delete post with id {}", id);
postService.deletePost(id);
}
@CacheEvict(value = "post-top")
@GetMapping("/top/evict")
public void evictTopPosts() {
log.info("Evict post-top");
}
Annotation ensure that post processor will check all beans trying to find demarcated methods and will create proxy to intercept all invocations.
Aggregate multiple annotations of the same type when e.g. you need to use different conditions and caches.
Class level annotation allows to specify global values for annotations like cache name or key generator.
One of the advantages of database abstraction layers such as ORM (object-relational mapping) frameworks is their ability to transparently cache data retrieved from the underlying store. This helps eliminate database-access costs for frequently accessed data.
As most other fully-equipped ORM frameworks, Hibernate has the concept of first-level cache. It is a session scoped cache which ensures that each entity instance is loaded only once in the persistent context.
Once the session is closed, first-level cache is terminated as well. This is actually desirable, as it allows for concurrent sessions to work with entity instances in isolation from each other.
On the other hand, second-level cache is SessionFactory-scoped, meaning it is shared by all sessions created with the same session factory. When an entity instance is looked up by its id (either by application logic or by Hibernate internally, e.g. when it loads associations to that entity from other entities), and if second-level caching is enabled for that entity, the following happens:
Once the instance is stored in the persistence context (first-level cache), it is returned from there in all subsequent calls within the same session until the session is closed or the instance is manually evicted from the persistence context. Also, the loaded instance state is stored in L2 cache if it was not there already.
import React from 'react';
import Button from 'antd/lib/button';
import styled from "styled-components";
const StyledButton = styled(Button)`
color: palevioletred;
font-weight: normal;
:focus {
color: palevioletred;
border-color: palevioletred;
}
:hover {
color: palevioletred;
border-color: palevioletred;
}
&.ant-btn-clicked:after {
content: '';
position: absolute;
top: -1px;
left: -1px;
bottom: -1px;
right: -1px;
border-radius: inherit;
border: 0 solid palevioletred;
opacity: 0.4;
-webkit-animation: buttonEffect 0.4s;
animation: buttonEffect 0.4s;
display: block;
}
`;
typescript-plugin-styled-components is a plugin for TypeScript that gives you a nicer debugging experience
// 1. import default from the plugin module
const createStyledComponentsTransformer = require('typescript-plugin-styled-components').default;
// 2. create a transformer;
// the factory additionally accepts an options object which described below
const styledComponentsTransformer = createStyledComponentsTransformer({ displayName: true});
// 3. add getCustomTransformer method to the loader config
var config = {
...
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
options: {
... // other loader's options
getCustomTransformers: () => ({ before: [styledComponentsTransformer] })
}
}
]
}
...
};
interface Options {
getDisplayName(filename: string, bindingName: string | undefined): string | undefined;
identifiers: CustomStyledIdentifiers;
ssr: boolean;
displayName: boolean;
minify: boolean;
}
使用new关键字创建
var obj = new Object()
obj.name = 'foo'
obj.age = 24
obj.say = function() {
console.log(`my name is ${this.name}, age is ${this.age}`)
}
使用字面量创建
var obj = {
name : "foo",
age: 24,
say: function() {
console.log(`my name is ${this.name}, age is ${this.age}`)
}
}
// obj 这个对象继承了 Object.prototype 上面的所有属性
// 所以可以这样使用 obj .hasOwnProperty('name').
// hasOwnProperty 是 Object.prototype 的自身属性。
// Object.prototype 的原型为 null。
// 原型链如下:
// o ---> Object.prototype ---> null
工厂模式
function createObj(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.say = function() {
console.log(`my name is ${this.name}, age is ${this.age}`)
}
return o;
}
var tom = createObj("tom", 12);
var jack = createObj("jack", 22);
构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.say = function() {
console.log(`my name is ${this.name}, age is ${this.age}`)
}
}
var tom = new Person("tom", 12);
var jack = new Person("jack", 22);
// tom 是生成的对象, 他的自身属性有 'name' 和 'age'.
// 在 tom 被实例化时,tom.[[Prototype]]指向了 Person.prototype.
// tome ---> Person.prototype---> Object.prototype ---> null
注意按照约定构造函数的首字母要大写。
在这里我们创建一个新对象过程如下:
注意:
new
调用构造函数,实质上与调用普通的方法函数无异,关键之处在于调用时,所处的作用域为全局作用域, this
指向了 window,也就是说,这次调用发生之后,平白无故的为 window
对象增添了两个属性 name 和 age。this
指向何处function CreateObj(x,y) {
if (this instanceof CreateObj) {
this.age = x;
this.name = y
} else {
return new CreateObj(x,y);
}
}
使用Object.create
Object.create(O[, Properties])
returns an object with a prototype of O and properties according to Properties (as if called through Object.defineProperties).
作用的效果可等价于
Object.create = function(O) {
function F() {};
F.prototype = O;
return new F();
}
var proto = {
say: function () {
console.log(`my name is ${this.name}, age is ${this.age}`)
}};
var p = Object.create(proto);
p.name = 'kate';
p.age = 12;
p.say() //my name is kate age is 12
Class can inherit methods, properties, and other characteristics from another class. When one class inherits from another, the inheriting class is known as a subclass, and the class it inherits from is known as its superclass.
Any class that does not inherit from another class is known as a base class.
Swift classes do not inherit from a universal base class. Classes you define without specifying a superclass automatically become base classes for you to build upon.
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// do nothing - an arbitrary vehicle doesn't necessarily make a noise
}
}
let someVehicle = Vehicle()
print("Vehicle: \(someVehicle.description)")
Subclassing is the act of basing a new class on an existing class. The subclass inherits characteristics from the existing class, which you can then refine. You can also add new characteristics to the subclass.
class SomeSubclass: SomeSuperclass {
// subclass definition goes here
}
class Bicycle: Vehicle {
var hasBasket = false
}
let bicycle = Bicycle()
bicycle.hasBasket = true
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour
A subclass can provide its own custom implementation of an instance method, type method, instance property, type property, or subscript that it would otherwise inherit from a superclass. This is known as overriding.
Where this is appropriate, you access the superclass version of a method, property, or subscript by using the super prefix:
An overridden method named someMethod() can call the superclass version of someMethod() by calling super.someMethod() within the overriding method implementation.
An overridden property called someProperty can access the superclass version of someProperty as super.someProperty within the overriding getter or setter implementation.
An overridden subscript for someIndex can access the superclass version of the same subscript as super[someIndex] from within the overriding subscript implementation.
// Overriding Methods
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}
// Overriding Property Getters and Setters
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}
// Overriding Property Observers
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
You can prevent a method, property, or subscript from being overridden by marking it as final.
Writing the final modifier before the method, property, or subscript’s introducer keyword (such as final var, final func, final class func, and final subscript).
You can mark an entire class as final by writing the final modifier before the class keyword in its class definition (final class). Any attempt to subclass a final class is reported as a compile-time error.
开发时经常会出现以下场景
请把你当前分支与master 最新版本做一次rebase,我们在合并你的PR
你把你几个commits squash成一个commit,避免git历史过于复杂和难看
你去把你commit 的message修改一下,使得commit 的信息让人更清晰易懂
上述的这些问题常见于PR下面的讨论,下面总结一下解决问题的方法
在提交后, 如果你发现在其描述中发现typo,或者找到一个更好的方式来描述commit。要进行校正:
git commit --amend
这时git将进入编辑模式,在这里你可以修改之前的commit 的相关信息
除了简单的编辑commit记录之外,你也可以单单修改coomit的author
git commit --amend --author="waltcow <[email protected]>"
git rebase
重新把每次的提交一个一个按顺序apply在当前的branch上。git rebase
接受几个使用的option,其中有个非常实用的就是 --interactive
(- i
是对应的shortcut),进入Interactive Rebase
模式后,git将打开编辑器,list出包含的改动信息,这些list的每行开始都接受command
,用户可以决定相关操作的参数后再进行rebase
详细可以见下面的相关例子
现在我要重写当前分支上最后4个commit的信息,我只需要
git rebase -i HEAD~4
下面我们将会看到
pick 07c5abd Introduce OpenPGP and teach basic usage
pick de9b1eb Fix PostChecker::Post#urls
pick 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend
# Rebase 8db7e8b..fa20af3 onto 8db7e8b
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
上面我们可以看到最近的commit记录,时间排序从older=>newer,
下面有相关的command的使用介绍,如下将逐一介绍
pick
这是默认的选择,就是把commit再重新apply一次而已,commit的内容和信息都不会有改动reword
commit的内容不变,仅仅把commit的message做修改比如我把上面的commit改成
pick 07c5abd Introduce OpenPGP and teach basic usage
pick de9b1eb Fix PostChecker::Post#urls
reword 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend
+
When I save and quit the editor, git will follow the described commands, landing
当我:wq
后git 将会输出
robots.thoughtbot.com tc-git-rebase % git rebase -i HEAD~4
[detached HEAD dd62a66] Stop all the highlighting
Author: Caleb Thompson
Date: Fri Oct 31 10:52:26 2014 -0500
2 files changed, 39 insertions(+), 42 deletions(-)
Successfully rebased and updated refs/heads/tc-git-rebase.
squash
将提交融合到前一个commit(上面的那一行)fixup
类似于 squash
,但是commit的message将会被丢弃下面是使用案例
现在我们我4个commit,前三个commit是我同事改的,我自己的是最后一个
pick 07c5abd Introduce OpenPGP and teach basic usage
pick de9b1eb Fix PostChecker::Post#urls
pick 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend
假如我现在想把我同事的修改合并在一起,以为前三个都是做一个的主题的,这样合并后的话,我们以后如果想revert
这部分变更将变简单。
前三个commit中我想只保留第一个commit的message,后面两个的更改提交并压缩到第一个提交消息中
pick 07c5abd Introduce OpenPGP and teach basic usage
squash de9b1eb Fix PostChecker::Post#urls
squash 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend
squash
的作用就是把当前commit向前合并,一直合并到pick
为止
fixup
和squash
非常类似,把pick
修改成fixup
,同样会向前合并到pick
,唯一的区别就是,fixup会忽略当前commit的信息,只会应用修改。
当我们fork一个远程仓库,在一个功能分支上开始工作,并且upstream/maste将不断的前进。我们的历史看起来像:
A---B---C feature
/
D---E---F---G upstream/master
当仓库管理者在合并你的PR时都会教你 rebase on top of master
,这样做可以避免merge
时出现冲突,并保证了两个分支是基于一个parent 的,在rebase后你所看到的历史是这样的
A'--B'--C' feature
/
D---E---F---G upstream/master
一般使用的命令
# Point our `upstream` remote to the original fork
git remote add upstream https://github.com/thoughtbot/factory_girl.git
# Fetch latest commits from `upstream` (the original fork)
git fetch upstream
# Checkout our feature branch
git checkout feature
# Reapply it onto upstream's master
git rebase upstream/master
# Fix conflicts, then `git rebase --continue`, repeat until done
# Push to our fork
git push --force origin feature
Behavior Subject is a type of subject, a subject is a special type of observable so you can subscribe to messages like any other observable
Behavior Subject
// a is a initial value. if there is a subscription
// after this, it would get "a" value immediately
let bSubject = new BehaviorSubject("a");
bSubject.next("b");
bSubject.subscribe((value) => {
console.log("Subscription got", value); // Subscription got b,
// ^ This would not happen
// for a generic observable
// or generic subject by default
});
bSubject.next("c"); // Subscription got c
bSubject.next("d"); // Subscription got d
Regular Subject
let subject = new Subject();
subject.next("b");
subject.subscribe((value) => {
console.log("Subscription got", value); // Subscription wont get
// anything at this point
});
subject.next("c"); // Subscription got c
subject.next("d"); // Subscription got d
项目中经常会在Closure使用unowned 和 weak 修饰 self 对象避免循环引用所带来的坑
Closure里面会用到self的地方,一般都是这样处理
{[unowned self] in }
但是在一个网络请求的Closure回调里面,这样后来发现在这个Closure执行之前, self 已经被释放了
{[weak self] in
if let weSelf = self{
执行代码
}
}
swift 会使用ARC(Automatic Reference Counting)帮我们打理内存,但是在有些时候,尤其是对象间存在互相引用的时候,就会由于reference cycle导致内存无法释放,而我们也需要时刻保持清醒,来避免内存泄露
Swift 使用“引用计数(reference count)”来管理类对象的生命周期,避免类对象在“仍被使用”的时候被意外释放。当一个对象的reference count为0时,Swift会立即删除该对象。
class Person {
let name: String
init(name: String) {
self.name = name print("\(name) is being initialized.")
}
deinit {
print("\(name) is being deinitialized.")
}
}
当执行以下的流程代码时:
var ref1: Person?
var ref2: Person?
ref1 = Person(name: "Mars") // count = 1
// Mars is being initialized.
ref2 = ref1 // count = 2
ref2 = nil // count = 1
ref1 = nil // count = 0
// Mars is being deinitialized.
对于Person这样的单个对象,ARC 可以很好的默默为我们工作。但是,当不同类对象之间存在相互引用时,指向彼此的是强引用,就会导致reference cycle, ARC无法释放它们之中的任何一个。
再看看下面的例子:
class Person {
let name: String
init(name: String) {
self.name = name print("\(name) is being initialized.")
}
deinit {
print("\(name) is being deinitialized.")
}
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
print("Apartment \(unit) is being initialized.")
}
deinit {
print("Apartment \(unit) is being deinitialized.")
}
}
执行以下的代码
var mars: Person? = Person(name: "Mars") // count = 1
// Mars is being initialized
var apt: Apartment? = Apartment(unit: "11") // count = 1
// Apartment 11 is being initialized
mars!.apartment = apt11
// mars.count = 2
apt!.tenant = mars
// Set mars and apartment to nil
mars = nil
apt = nil
尽管我们把mars和apt设置为nil,Person和Apartmetn 的 deinit也不会被调用了。因为它们的两个member(apartment和tenant)是一个强引用,指向了彼此,让对象仍旧“存活”在内存里。但是,mars和apt已经被设置成nil,我们也已经无能为力了。这就是类对象之间的reference cycle。
Apartment 和 Person 中引起reference cycle数据成员都允许为nil,对于这种情况,我们可以像下面这样使用weak来解决 reference cycle:
weak var name: Type
class Apartment {
let unit: String
weak var tenant: Person?
// omit for simplicity...
}
var mars: Person? = Person(name: "Mars")
var apt: Apartment? = Apartment(unit: "11", owner: mars!)
mars!.apartment = apt
apt!.tenant = mars
mars = nil //由于已经没有strong reference指向**mars**,于是**mars**就被**ARC**释放了。
apt = nil // 这时,也没有任何strong reference指向apt了,它也会被ARC释放。
在闭包和捕获的实例总是相互引用并且同时销毁时,将闭包内的捕获定义为unowned(无主引用)。
相反的,在被捕获的引用可能会变为nil时,将闭包内的捕获定义为 weak(弱引用)。
那什么情况下需要用到weak呢?
class ViewController: UIViewController {
var workItem = DispatchWorkItem(block: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.updateUI()
})
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now() + 2.33, execute: workItem)
}
func updateUI() {
// ...
}
}
上面是个延时操作的例子,如果声明为unowned(无主引用),在还没有到达延迟的时间的时候,我们 pop 掉这个 ViewController 的话,程序就会 crash。因为无主引用是非可选类型,pop 掉这个 ViewController 后,闭包中捕获的 self (也就是ViewController的实例)已经被销毁了,这时候再访问被销毁的实例,程序肯定会奔溃的。
所以这里应该声明为weak(弱引用),弱引用总是可选类型,当引用的实例被销毁后,弱引用的值会自动置为 nil,并且我们可以在闭包内检查它们是否存在。
Properties
associate values with a particular class, structure, or enumeration.
Computed properties are provided by classes, structures, and enumerations. Stored properties are provided only by classes and structures.
Stored and computed properties are usually associated with instances of a particular type.
Properties can also be associated with the type itself. Such properties are known as type properties
.
Define property observers to monitor changes in a property’s value
stored property
is aconstant
orvariable
that is stored as part of an instance of a particular class or structure. Stored properties can be introduced by thevar
orlet
keywordDefault value can be provided for a stored property as part of its definition
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// the range represents integer values 0, 1, and 2
rangeOfThreeItems.firstValue = 6
// the range now represents integer values 6, 7, and 8
If you create an instance of a structure and assign that instance to a constant, you cannot modify the instance’s properties, even if they were declared as variable properties:
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// this range represents integer values 0, 1, 2, and 3
rangeOfFourItems.firstValue = 6
// this will report an error, even though firstValue is a variable property
A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy modifier before its declaration.
class DataImporter {
/*
DataImporter is a class to import data from an external file.
The class is assumed to take a nontrivial amount of time to initialize.
*/
var filename = "data.txt"
// the DataImporter class would provide data importing functionality here
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// the DataManager class would provide data management functionality here
}
print(manager.importer.filename)
// the DataImporter instance for the importer property has now been created
// Prints "data.txt"
Computed properties
which do not actually store a value. Instead, they provide a getter and an optional setter to retrieve and set other properties and values indirectly.
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// Prints "square.origin is now at (10.0, 10.0)"
computed property’s setter does not define a name for the new value to be set, a default name of
newValue
is used.
struct AlternativeRect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
A computed property with a getter but no setter is known as a read-only computed property. A read-only computed property always returns a value, and can be accessed through dot syntax
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// Prints "the volume of fourByFiveByTwo is 40.0"
Property observers
observe and respond to changes in a property’s value. Property observers are called every time a property’s value is set, even if the new value is the same as the property’s current value.
willSet
is called just before the value is stored.
didSet
is called immediately after the new value is stored.
If you implement a
willSet
observer, it’s passed the new property value as a constant parameter. You can specify a name for this parameter as part of your willSet implementation.
If you don’t write the parameter name and parentheses within your implementation, the parameter is made available with a default parameter name of
newValue
.
If you implement a
didSet
observer, it’s passed a constant parameter containing the old property value. You can name the parameter or use the default parameter name ofoldValue
. If you assign a value to a property within its owndidSet
observer, the new value that you assign replaces the one that was just set.
Instance properties are properties that belong to an instance of a particular type. Every time you create a new instance of that type, it has its own set of property values, separate from any other instance.
You define type properties with the
static
keyword. For computed type properties for class types, use theclass
keyword instead to allow subclasses to override the superclass’s implementation.
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
StackEdit是一个开放源码免费使用的 MarkDown 编辑器, 基于 PageDown,使用了 Stack Overflow 和其他 Stack Exchange 网站的 MarkDown 库,stackoverflow出品。
StackEdit是一个开放源码免费使用的 MarkDown 编辑器, 基于 PageDown,使用了 Stack Overflow 和其他 Stack Exchange 网站的 MarkDown 库,stackoverflow出品。
展示页面: https://stackedit.io/.
这款在线markdown编辑器,是一款很赞的应用。如果你是markdown的用户,那么你要试一下这款应用;如果你是chrome用户,那么你更要安装这款应用。我是把它作为chrome的一个插件应用来使用的。下面来看一下这款应用的特点及使用技巧。
FROM ubuntu:14.04
RUN apt-get update \
&& apt-get install -y curl git \
&& curl -sL https://deb.nodesource.com/setup_0.12 | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/* \
&& git clone https://github.com/benweet/stackedit.git
WORKDIR /stackedit
RUN npm install \
&& npm install bower \
&& node_modules/bower/bin/bower install --production --config.interactive=false --allow-root
EXPOSE 3000
CMD nodejs server.js
build Image
docker build -t stackedit .
run container
docker run -d -p 3000:3000 stackedit
var theData = [ 1, 2, 3 ]
var p = d3.select("body").selectAll("p")
.data(theData)
.enter()
.append("p")
.text("hello ");
会生成以下的结构
<body>
<p>hello </p>
<p>hello </p>
<p>hello </p>
</body>
selectAll 方法使用类似css选择器的语法去抓取DOM元素,
不像select方法那样(只返回第一个选中的元素),
它将选中所有match中的elements,
但是当前的HTML 文档流上面根本不存在<p>
元素啊,它究竟做了什么鬼,
其实它选中的是所有即将可用的<p>
而已,在此时,p是不存在的,
它返回了一个空的selection
再后面的用到了.data(theData)
和 enter()
后 ,我们就可以把数据和空的selection
进行绑定
data
操作符返回了三个 virtual selections
这三个分别是
enter selection
它包含了一些确是元素的placeholder
update selection
它包含了一些存在的元素和data进行数据的绑定
exit selection
它包含剩下被删除的元素
Yarn
是Node.js平台的代码包管理器。类似于知名的npm包管理器,实际是npm客户端。
yarn:/jɑːn/,纱线,奇谈,故事。
Facebook 推出了一款名叫 Yarn
的包管理器,声称比现有的 npm 客户端更快,更可靠,更安全。Yarn
可以将安装时间从数分钟减少至几秒钟。Yarn
还兼容npm注册表,但包安装方法有所区别。其使用了lockfiles和一个决定性安装算法,能够为参与一个项目的所有用户维持相同的节点模块(node_modules)目录结构,有助于减少难以追踪的bug和在多台机器上复制。
npm 的速度实在实在太慢了。它或许没有什么太大的功能缺陷,但是真的,它太慢了。仅仅因为这个,和 yarn 一对比,几乎没有多少人能回去。因为 yarn 不仅飞快、扩展了功能,而且还保证了对 npm 的兼容。
// Linux
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb http://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update && sudo apt-get install yarn
//MacOS
brew update
brew install yarn
// Windows
下载msi安装包。确保Node.js已经安装并可用。
下载Yarn安装包 https://yarnpkg.com/en/docs/install#windows-tab
最后检查安装是否成功:
yarn --version
国内加速,换上taobao的registry,
yarn config set registry "https://registry.npm.taobao.org"
yarn init
添加依赖
// 把包添加到package.json里的dependencies,也同时会更新yarn.lock
yarn add [package] # 通过名称添加一个依赖包
yarn add [package]@[version] #“包名@版本号”格式
yarn add [package]@[tag] #“包名@标签”格式
yarn add --dev/-D #添加到devDependencies
yarn add --peer/-P #添加到peerDependencies
yarn add --optional/-O # 添加到optionalDependencies
安装依赖包
# 从package.json里提取所有的依赖并安装,然后生成yarn.lock锁定所有的依赖版本,
# 别人执行yarn install时会根据yarn.lock安装依赖,保证不同的电脑安装的依赖目录结构完全一致。
yarn install
# 有且仅有一个依赖的版本被允许,多依赖会出现一个交互式窗口,让使用者选择某一个版本安装
yarn install --flat
#强制重新下载所有的依赖包
yarn install --force
#只下载dependencies下的依赖
yarn install --production
更新依赖包
yarn upgrade [package]
yarn upgrade [package]@[version]
yarn upgrade [package]@[tag]
删除依赖包
// 删除某个依赖,并自动更新package.json和yarn.lock文件
yarn remove [package]
全局命令
在yarn命令前加一个global修饰,可以将命令变为全局的,支持的命令有 add,bin,ls,remove,upgrade
例如 , npm install gulp -g
可以用yarn global add gulp
来替代
npm 命令 | Yarn 命令 | 说明 |
---|---|---|
npm install | yarn install | 安装依赖包 |
(N/A) | yarn install --flat | 单版本模式 |
(N/A) | yarn install --har | 生成har文件,记录安装时网络请求性能 |
(N/A) | yarn install --no-lockfile | 不读写lockfile方式 |
(N/A) | yarn install --pure-lockfile | 不生成yarn.lock文件 |
npm install [package] | (N/A) | 安装依赖 |
npm install --save [package] | yarn add [package] | 添加生产模式依赖到项目 |
npm install --save-dev [package] | yarn add [package] [--dev/-D] | 添加开发模式的依赖 |
(N/A) | yarn add [package] [--peer/-P] | 对等模式添加依赖,发布/分享项目时的依赖 |
npm install --save-optional [package] | yarn add [package] [--optional/-O] | |
npm install --save-exact [package] | yarn add [package] [--exact/-E] | |
(N/A) | yarn add [package] [--tilde/-T] | |
npm install --global [package] | yarn global add [package] | |
npm rebuild | yarn install --force | |
npm uninstall [package] | (N/A) | |
npm uninstall --save [package] | yarn remove [package] | |
npm uninstall --save-dev [package] | yarn remove [package] | |
npm uninstall --save-optional [package] | yarn remove [package] | |
npm cache clean | yarn cache clean | |
rm -rf node_modules && npm install | yarn upgrade |
关系型数据库都是有权限控制的,什么用户能访问什么库,什么表,什么用户可以插入,更新,而有的用户只有读取权限。
> show dbs #显示数据库列表
> show collections #显示当前数据库中的集合(类似关系数据库中的表)
> show users #显示用户
> use <db name> #切换当前数据库,如果数据库不存在则创建数据库。
> db.help() #显示数据库操作命令,里面有很多的命令
> db.foo.help() #显示集合操作命令,同样有很多的命令,foo指的是当前数据库下,一个叫foo的集合,并非真正意义上的命令
> db.foo.find() #对于当前数据库中的foo集合进行数据查找(由于没有条件,会列出所有数据)
> db.foo.find( { a : 1 } ) #对于当前数据库中的foo集合进行查找,条件是数据中有一个属性叫a,且a的值为1
> db.dropDatabase() #删除当前使用数据库
> db.cloneDatabase("127.0.0.1") #将指定机器上的数据库的数据克隆到当前数据库
> db.copyDatabase("mydb", "temp", "127.0.0.1") #将本机的mydb的数据复制到temp数据库中
> db.repairDatabase() #修复当前数据库
> db.getName() #查看当前使用的数据库,也可以直接用db
> db.stats() #显示当前db状态
> db.version() #当前db版本
> db.getMongo() #查看当前db的链接机器地址
> db.serverStatus() #查看数据库服务器的状态
如果你想创建一个“myTest”的数据库,先运行use myTest命令,之后就做一些操作(如:db.createCollection('user')),这样就可以创建一个名叫“myTest”的数据库。
> use admin
> show collections
> db.createUser(
{
user: "admin",
pwd: "password",
roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
}
)
Successfully added user: {
"user" : "admin",
"roles" : [
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
}
]
}
> show users
> db.system.users.find()
$ mongod --auth
> show dbs
2016-07-19T17:32:37.258+0800 E QUERY [thread1] Error: listDatabases failed:{
"ok" : 0,
"errmsg" : "not authorized on admin to execute command { listDatabases: 1.0 }",
"code" : 13
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:62:1
shellHelper.show@src/mongo/shell/utils.js:760:19
shellHelper@src/mongo/shell/utils.js:650:15
@(shellhelp2):1:1
> use admin
switched to db admin
> db.auth('admin','mongodb:passok')
1
> show dbs
admin 0.000GB
local 0.000GB
> use mydb
switched to db mydb
> db.createUser(
{
user: "tonny",
pwd: "tonny@passok",
roles: [ { role: "readWrite", db: "mydb" } ]
}
)
Successfully added user: {
"user" : "tonny",
"roles" : [
{
"role" : "readWrite",
"db" : "mydb"
}
]
}
> exit
bye
角色说明:
Read:允许用户读取指定数据库
readWrite:允许用户读写指定数据库
dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
root:只在admin数据库中可用。超级账号,超级权限
最近看了几篇wechat 数据库解密的分享, 感觉很有意思尝试了一下,现在此进行总结整理。
PC版微信的聊天记录数据库是放在本地的,其目录为 \文档 \WeChat Files\用户名\Msg
。数据库是经过sqlcipher加密过的,密码生成算法在内存中加密的。当在微信登录过程中,可以用Ollydbg在内存中找到解密的秘钥,通过给出的解密算法便可以解密出内容(通过sqlcipher也可以解密)。Msg目录中的.db文件都可以解密,其中ChatMsg.db
存放的是聊天记录
下载并安装Ollydbg
启动微信,停留在登录界面, 先不要登录,用 Ollydbg 附加微信,进程名WeChat
在外部模块 【Alt + E】 找到 wechatwin.dll 模块
搜索字符串
定位到 ”DBFactory::encryptDB”
在test edx, edx
处下断点【F2】
【Ctrl + F2】 重新运行起来,登录微信,跑入刚刚下的断点处,edx中即为秘钥地址,秘钥长度32位。
【Ctrl + G】输入内存地址,定位内存,复制出32 位长度的秘钥,记下来留在下面解密用到
安装好C++ IDE, 下面以CLion为例
解密需要用到openssl库,win10请用1.0.2版本,为避免各种问题,可以直接使用slproweb提供的安装版,切勿使用名字含Light的轻量版 , 下面以Win32OpenSSL-1_0_2p.exe 这个版本为例, 安装路径选择 C:\OpenSSL-Win32
CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(wechat_db_decrypt)
set(CMAKE_CXX_STANDARD 14)
set(INC_DIR C:\\OpenSSL-Win32\\include)
set(LINK_DIR C:\\OpenSSL-Win32\\lib)
include_directories(${INC_DIR})
link_directories(${LINK_DIR})
link_directories(C:\\OpenSSL-Win32\\lib\\MinGW)
add_executable(wechat_db_decrypt main.cpp)
target_link_libraries(wechat_db_decrypt eay32)
main.cpp
#include <Windows.h>
#include <openssl/rand.h>
#include <openssl/evp.h>
#include <openssl/aes.h>
#include <openssl/hmac.h>
//#define ANDROID_WECHAT
#define SQLITE_FILE_HEADER "SQLite format 3" //length == 16
#define IV_SIZE 16
#define HMAC_SHA1_SIZE 20
#define KEY_SIZE 32
#ifndef ANDROID_WECHAT
#define DEFAULT_PAGESIZE 4096
#define DEFAULT_ITER 64000
#else
#define NO_USE_HMAC_SHA1
#define DEFAULT_PAGESIZE 1024
#define DEFAULT_ITER 4000
#endif
//安卓端这里密码是7位,pc端是经过算法得到的32位pass,这里的0xcc是我隐去的部分,实际不会有0xcc
unsigned char pass[] = {
0xD7, 0xB9, 0x54, 0xED, 0x71, 0x97, 0x40, 0xDF, 0xA5, 0xC0, 0x91, 0x16, 0x08, 0x04, 0xF5, 0xF4,
0xCA, 0x0C, 0x65, 0x0B, 0x8A, 0x0B, 0x45, 0xCA, 0x80, 0xA1, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc
};
int main()
{
FILE *fpdb = fopen("MicroMsg.db", "rb+");
if (!fpdb) {
printf("error open file");
return 0;
}
fseek(fpdb, 0, SEEK_END);
long nFileSize = ftell(fpdb);
fseek(fpdb, 0, SEEK_SET);
unsigned char *pDbBuffer = new unsigned char[nFileSize];
fread(pDbBuffer, 1, nFileSize, fpdb);
fclose(fpdb);
unsigned char salt[16] = { 0 };
memcpy(salt, pDbBuffer, 16);
#ifndef NO_USE_HMAC_SHA1
unsigned char mac_salt[16] = { 0 };
memcpy(mac_salt, salt, 16);
for (int i = 0; i < sizeof(salt); i++) {
mac_salt[i] ^= 0x3a;
}
#endif
int reserve = IV_SIZE;
#ifndef NO_USE_HMAC_SHA1
reserve += HMAC_SHA1_SIZE;
#endif
reserve = ((reserve % AES_BLOCK_SIZE) == 0) ? reserve : ((reserve / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;
unsigned char key[KEY_SIZE] = { 0 };
unsigned char mac_key[KEY_SIZE] = { 0 };
OpenSSL_add_all_algorithms();
PKCS5_PBKDF2_HMAC_SHA1((const char *)pass, sizeof(pass), salt, sizeof(salt), DEFAULT_ITER, sizeof(key), key);
#ifndef NO_USE_HMAC_SHA1
PKCS5_PBKDF2_HMAC_SHA1((const char *)key, sizeof(key), mac_salt, sizeof(mac_salt), 2, sizeof(mac_key), mac_key);
#endif
unsigned char *pTemp = pDbBuffer;
unsigned char pDecryptPerPageBuffer[DEFAULT_PAGESIZE];
int nPage = 1;
int offset = 16;
while (pTemp < pDbBuffer + nFileSize) {
printf("decrypt page:%d/%d \n", nPage, nFileSize / DEFAULT_PAGESIZE);
#ifndef NO_USE_HMAC_SHA1
//check hmac
unsigned char hash_mac[HMAC_SHA1_SIZE] = { 0 };
unsigned int hash_len = 0;
HMAC_CTX hctx;
HMAC_CTX_init(&hctx);
HMAC_Init_ex(&hctx, mac_key, sizeof(mac_key), EVP_sha1(), NULL);
HMAC_Update(&hctx, pTemp + offset, DEFAULT_PAGESIZE - reserve - offset + IV_SIZE);
HMAC_Update(&hctx, (const unsigned char *)&nPage, sizeof(nPage));
HMAC_Final(&hctx, hash_mac, &hash_len);
HMAC_CTX_cleanup(&hctx);
if (0 != memcmp(hash_mac, pTemp + DEFAULT_PAGESIZE - reserve + IV_SIZE, sizeof(hash_mac))) {
//hash check err
return 0;
}
#endif
//
if (nPage == 1) {
memcpy(pDecryptPerPageBuffer, SQLITE_FILE_HEADER, offset);
}
//aes decrypt
EVP_CIPHER_CTX* ectx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ectx, EVP_get_cipherbyname("aes-256-cbc"), NULL, NULL, NULL, 0);
EVP_CIPHER_CTX_set_padding(ectx, 0);
EVP_CipherInit_ex(ectx, NULL, NULL, key, pTemp + (DEFAULT_PAGESIZE - reserve), 0);
int nDecryptLen = 0;
int nTotal = 0;
EVP_CipherUpdate(ectx, pDecryptPerPageBuffer + offset, &nDecryptLen, pTemp + offset, DEFAULT_PAGESIZE - reserve - offset);
nTotal = nDecryptLen;
EVP_CipherFinal_ex(ectx, pDecryptPerPageBuffer + offset + nDecryptLen, &nDecryptLen);
nTotal += nDecryptLen;
EVP_CIPHER_CTX_free(ectx);
//assert(nTotal == DEFAULT_PAGESIZE - reserve - offset);
//no necessary ,just like sqlcipher
memcpy(pDecryptPerPageBuffer + DEFAULT_PAGESIZE - reserve, pTemp + DEFAULT_PAGESIZE - reserve, reserve);
FILE *fp = fopen("MicroMsg_Decrypt.db", "ab+");
{
fwrite(pDecryptPerPageBuffer, 1, DEFAULT_PAGESIZE, fp);
fclose(fp);
}
nPage++;
offset = 0;
pTemp += DEFAULT_PAGESIZE;
}
return 0;
}
解密出的 xxx_Decrypt.db 文件可通过 Navicat Premium 数据库可视化工具查看。
Git是一个“分布式版本管理工具”,简单的理解版本管理工具:大家在写东西的时候都用过“回撤”这个功能,但是回撤只能回撤几步,假如想要找回我三天之前的修改,光用“回撤”是找不回来的。而“版本管理工具”能记录每次的修改,只要提交到版本仓库,你就可以找到之前任何时刻的状态(文本状态)。
下面的内容就是列举了常用的Git命令和一些小技巧,可以通过"页面内查找"的方式进行快速查询:Ctrl/Command+f
。
如果之前未使用过Git,可以学习廖老师的免费Git教程入门
git version 2.7.4 (Apple Git-66)
下测试通过git add 改动的文件名
,此次改动就放到了‘暂存区’git commit 此次修改的描述
,此次改动就放到了’本地仓库’,每个commit,我叫它为一个‘版本’。git push 远程仓库
,此次改动就放到了‘远程仓库’(GitHub等)git log
,最上面那行commit xxxxxx
,后面的字符串就是commit-id.gitignore
文件中记录的文件git help -g
抛弃本地所有的修改,回到远程仓库的状态。
git fetch --all && git reset --hard origin/master
也就是把所有的改动都重新放回工作区,并清空所有的commit,这样就可以重新提交第一个commit了
git update-ref -d HEAD
输出工作区和暂存区的different(不同)。
git diff
还可以展示本地仓库中任意两个commit之间的文件变动:
git diff <commit-id> <commit-id>
输出暂存区和本地最近的版本(commit)的different(不同)。
git diff --cached
输出工作区、暂存区 和本地最近的版本(commit)的different(不同)。
git diff HEAD
git checkout -
git branch --merged master | grep -v '^\*\| master' | xargs -n 1 git branch -d
git branch -vv
关联之后,git branch -vv
就可以展示关联的远程分支名了,同时推送到远程仓库直接:git push
,不需要指定远程仓库了。
git branch -u origin/mybranch
或者在push时加上-u
参数
git push origin/mybranch -u
-r参数相当于:remote
git branch -r
-a参数相当于:all
git branch -a
git checkout -b <branch-name>
git checkout -b <branch-name> origin/<branch-name>
git branch -d <local-branchname>
git push origin --delete <remote-branchname>
或者
git push origin :<remote-branchname>
git branch -m <new-branch-name>
git tag
展示当前分支的最近的tag
git describe --tags --abbrev=0
git tag <version-number>
默认tag是打在最近的一次commit上,如果需要指定commit打tag:
$ git tag -a <version-number> -m "v1.0 发布(描述)" <commit-id>
首先要保证本地创建好了标签才可以推送标签到远程仓库:
git push origin <local-version-number>
一次性推送所有标签,同步到远程仓库:
git push origin --tags
git tag -d <tag-name>
删除远程标签需要先删除本地标签,再执行下面的命令:
git push origin :refs/tags/<tag-name>
一般上线之前都会打tag,就是为了防止上线后出现问题,方便快速回退到上一版本。下面的命令是回到某一标签下的状态:
git checkout -b branch_name tag_name
git checkout <file-name>
放弃所有修改:
git checkout .
git revert <commit-id>
和revert的区别:reset命令会抹去某个commit id之后的所有commit
git reset <commit-id>
git commit --amend
git log
blame的意思为‘责怪’,你懂的。
git blame <file-name>
就像shell的history一样
git reflog
git commit --amend --author='Author Name <[email protected]>'
git remote set-url origin <URL>
git remote add origin <remote-url>
git remote
git whatchanged --since='2 weeks ago'
这个过程需要cherry-pick
命令,参考
git checkout <branch-name> && git cherry-pick <commit-id>
简化命令
git config --global alias.<handle> <command>
比如:git status 改成 git st,这样可以简化命令
git config --global alias.st status
详解可以参考廖雪峰老师的git教程
git stash
untracked文件:新建的文件
git stash -u
git stash list
git stash apply <stash@{n}>
git stash pop
git stash clear
git checkout <stash@{n}> -- <file-path>
git ls-files -t
git ls-files --others
git ls-files --others -i --exclude-standard
可以用来删除新建的文件。如果不指定文件文件名,则清空所有工作的untracked文件。clean
命令,注意两点:
git clean <file-name> -f
可以用来删除新建的目录,注意:这个命令也可以用来删除untracked的文件。详情见上一条
git clean <directory-name> -df
git log --pretty=oneline --graph --decorate --all
git bundle create <file> <branch-name>
新建一个分支,分支内容就是上面git bundle create
命令导出的内容
git clone repo.bundle <repo-dir> -b <branch-name>
git rebase --autostash
git fetch origin pull/<id>/head:<branch-name>
git diff --word-diff
git clean -X -f
注意: config分为:当前目录(local)和全局(golbal)的config,默认为当前目录的config
git config --local --list (当前目录)
git config --global --list (全局)
git status --ignored
git log Branch1 ^Branch2
git log --show-signature
git config --global --unset <entry-name>
相当于保存修改,但是重写commit历史
git checkout --orphan <branch-name>
git show <branch-name>:<file-name>
git clone -b <branch-name> --single-branch https://github.com/user/repo.git
关闭 track 指定文件的改动,也就是 Git 将不会在记录这个文件的改动
git update-index --assume-unchanged path/to/file
恢复 track 指定文件的改动
git update-index --no-assume-unchanged path/to/file
不再将文件的权限变化视作改动
git config core.fileMode false
最新的放在最上面
git for-each-ref --sort=-committerdate --format='%(refname:short)' refs/heads/
通过grep查找,given-text:所需要查找的字段
git log --all --grep='<given-text>'
git reset <file-name>
git push -f <remote-name> <branch-name>
electron-builder 在构建的时候仅仅把production的依赖部分重新rebuild而已,所以有时需要两个package.json 文件的结构
Koa 是NodeJS面向下一代的服务器框架,下面主要介绍如何使用JWT(json web token) 保护私有的API
JWT 代表JSON web token。 它是一种用于交换安全信息的标准令牌,主要用于身份验证。 JSON web token 体积非常的小,它可以通过下面几种方式传输:
Token 由三部分构成,以 . 作为分割符号
SIGNATURE
是使用HEADER
,PAYLOAD
和SECRET KEY
来生成, 因此它可以用于检查PAYLOAD
没有被改变。JWT的格式一般是这样: <header>.<payload>.<signature>
在使用身份认证的场景里,JWT 一般以下面的流程进行的
登录授权部分
用户已授权后的请求场景
用户无法改变PLAYLOAD里面的数据来伪造其他用户的信息,因为PLAYLOAD也是用于生产签名的factor之一,修改后服务器认证将无法通过
JWT的身份验证机制的主要优点就是不需要依赖于SESSION
。
TOKEN像访问卡,它是存储的就是它用户的信息,通常在cookie或local storage。后端不保存已经签发过的TOKEN。
SECRET KEY
不与用户共享,它只存储在服务器上,这使得它不太可能被黑客破解。
JWT
也比其他SAML令牌体积要小。这使它更适合于在HTML和HTTP环境中传递。
JWT
一个主要缺点是,当恶意用户可以拿到别人的一个有效的令牌后,可以直接用别人的TOKEN作为请求,这就是为什么要使用SSL来保护在每个请求上发送的令牌的重要原因。
A厂有一套HTTP接口需要提供给B厂使用,由于是外网环境,那么就需要有一套安全机制保障
oauth2
就可以作为一个方案
Third-party application:第三方应用程序,又称"客户端"(client)
HTTP service:HTTP服务提供商,
Resource Owner:资源所有者, 又称"用户"(user)
User Agent:用户代理,本文中就是指浏览器。
Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
RFC 6749
(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。
Babel 把用最新标准编写的 JavaScript 代码(ES6, ES7)向下编译成可以在今天随处可用的版本(ES3,ES5)。 这一过程叫做“源码到源码”编译, 也被称为转换编译。并且还可以支持React的JSX写法。
15 年 11 月,Babel 发布了 6.0 版本。相较于前一代 Babel 5,新一代 Babel 更加模块化, 将所有的转码功能以插件的形式分离出去,默认只提供 babel-core。原本只需要装一个 babel ,现在必须按照自己的需求配置,无需下载大量无用的依赖
因为 Babel 的不同版本以及不同转码规则会起到不同的效果,全局安装会带来不必要的麻烦。在命令提示符中转到自己的项目目录下:
$ npm install --global babel-cli
# or
$ npm install --save-dev babel-core
babel-cli
babel-core
babel-core
的作用是把js
代码分析成ast
,方便各个插件分析语法进行相应的处理。有些新语法在低版本 js 中是不存在的,如箭头函数,rest 参数,函数默认值等,这种语言层面的不兼容只能通过将代码转为 ast,分析其语法后再转为低版本js
。首先安装babel-core
。
如果你之前执行了全局安装,可以通过下面的代码卸载全局范围的 Babel。放心,这并不会影响到项目路径下的本地安装。
npm uninstall --global babel-cli
因为在babel6
中没有默认的转换规则,当你没加任何配置去转换一个文件时,babel
只会把文件直接原码输出,不做任何的改变
如果你想转义一些相关的新特性,例如 arrow function
你需要先安装arrow function
的相关plugin
npm install --save-dev babel-plugin-transform-es2015-arrow-functions
然后修改本地的.babelrc
文件,加入以下的配置
{
"plugins": ["transform-es2015-arrow-functions"]
}
// before
var foo = () => {
console.log('foo')
}
//after
'use strict';
var foo = function foo() {
console.log('foo');
};
babel的plugin是往下兼容的,比如在
ES2015
中常量是let
,如果你想在ES5中运作则需额外的添加plugin编译
$ npm install --save-dev babel-plugin-check-es2015-constants
$ npm install --save-dev babel-plugin-transform-es2015-block-scoping
{
"plugins": [
"check-es2015-constants",
"transform-es2015-block-scoping"
]
}
由于这些的依赖很难去记住,如果你不想刻意去定制,你只要配一下preset
(就是一堆插件的集合)
preset
类似于餐厅的套餐。如 babel-preset-es2015
打包了 es2015 的特性,babel-preset-stage-0
打包处于Strawman 初稿
阶段的语法,babel-preset-react
包含了React的转码规则
以下是babel
在4 个不同阶段的预设:
注意 stage-4 预设是不存在的因为它就是上面的 es2015 预设。
JavaScript 还有一些提案,正在积极通过 TC39(ECMAScript 标准背后的技术委员会)的流程成为标准的一部分。
这个流程分为 5(0-4)个阶段。 随着提案得到越多的关注就越有可能被标准采纳,于是他们就继续通过各个阶段,最终在阶段 4 被标准正式采纳。
一个推进 ECMAScript 发展的自由形式的想法。该想法必须由 TC39 的会员提交,如果是非会员则必须注册成为 TC39 贡献者才能提交。
必须确定一位带头人来为负责这份建议。无论是带头人或者联合带头人都必须是 TC39 的会员(原文)。建议要解决的问题必须以简明的文字描述,而解决方案则要给出相应的实例和 API,并详细描述语义及算法。最后,必须指明此建议的潜在问题,例如与其他特性之间的关联,实现难点等。
草案是规范的第一个版本。其与最终标准中包含的特性不会有太大差别。建议此时必须要附加该特性的语法和语义的正式说明(使用 ECMAScript 标准的形式语言)。说明应该尽可能完善,但可以包含待办事项和占位符。该特性需要两个实验性的实现,其中一个可以在类似 Babel 的转译器(transpiler)中实现。
候选阶段,建议基本完成,此时将从实现过程和用户使用两方面获取反馈来进一步完善建议。必备条件:规范文档必须是完整的。指定的评审人(由 TC39 而不是带头人指定)和 ECMAScript 规范的编辑须在规范上签字。还有至少要两个符合规范的实现(不必指定默认实现)。
建议已经准备就绪,可以添加到标准之中。
Babel 默认只转码 ES6 的新语法(syntax),而不转换新的API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign、Array.from)都不会转码。如果想让这写方法运行, babel 社区提供两种方案可以选择
两个方案功能几乎相同,就是转码新增 API ,模拟 ES6 环境,但实现方法完全不同
babel-polyfill
的做法是将全局对象通通污染一遍,比如想在 node 0.10 上用 Promise,调用 babel-polyfill
就会往 global 对象挂上Promise
对象。对于普通的业务代码没有关系,但如果用在模块上就有问题了,会把模块使用者的环境污染掉。
babel-runtime
的作用也是模拟 ES2015 环境。只不过,babel-polyfill
是针对全局环境的,引入它,我们的浏览器就好像具备了规范里定义的完整的特性 – 虽然原生并未实现。babel-runtime
更像是分散的 polyfill 模块,我们可以在自己的模块里单独引入,比如 require(‘babel-runtime/core-js/promise’)
,它们不会在全局环境添加未实现的方法,只是,这样手动引用每个 polyfill
会非常低效。我们借助 Runtime transform 插件来自动化处理这一切。
$ npm install --save-dev babel-plugin-transform-runtime
$ npm install --save babel-runtime
// babelrc文件
{
"plugins": [
"transform-runtime",
"transform-es2015-classes"
]
}
现在,Babel 会把这样的代码:
class Foo {
method() {}
}
编译成:
import _classCallCheck from "babel-runtime/helpers/classCallCheck";
import _createClass from "babel-runtime/helpers/createClass";
let Foo = function () {
function Foo() {
_classCallCheck(this, Foo);
}
_createClass(Foo, [{
key: "method",
value: function method() {}
}]);
return Foo;
}();
VirturalBox为每个虚拟机提供八种虚拟的PCI 网卡,对于每一种虚拟网卡,你可以从下列六种网络硬件中任选一种:
AMD PCNet PCI II (Am79C970A)
AMD PCNet FAST III (Am79C973, the default)
Intel PRO/1000 MT Desktop (82540EM)(Windows Vista and later versions)
Intel PRO/1000 T Server (82543GC)(Windows XP)
Intel PRO/1000 MT Server (82545EM)(OVF imports from other platforms)
Paravirtualized network adapter (virtio-net)
特点:
应用场景:
配置方法:
连接方式 选择 网络地址转换(NAT)
高级-控制芯片 选择 PCnet-FAST III
高级-混杂模式 拒绝
高级-接入网线 √ (虚拟机ip自动获取)
ip样式:
ip 10.0.2.15
网关 10.0.2.2
DNS 10.0.2.3
原理:
特点:
应用场景:
配置方法:
连接方式 选择 桥接网卡
界面名称 选择 (如果你的笔记本有无线网卡和有线网卡,需要根据现在的上网方式对应选择)
高级-控制芯片 选择 PCnet-FAST III
高级-混杂模式 拒绝
高级-接入网线 √(虚拟机ip自动获取)
原理:
特点:
应用场景:
配置方法:
连接方式 选择 仅主机(Host-Only)适配器
界面名称 选择 VirtualBox Host-Only Ethernet Adapter
如果无法设置界面名称,可以:In VirtualBox > Preferences > Network, set up a host-only network
高级-控制芯片 选择 PCnet-FAST III
高级-混杂模式 拒绝
高级-接入网线 √
(虚拟机ip自动获取,也可以自己进行配置,网关配置为主机中虚拟网卡的地址【默认为192.168.56.1】,ip配置为与虚拟网卡地址同网段地址)
Add Hot Reloading to Xcode 9
Injection App
Elegant iOS form builder in Swift
Creating UIViews programmatically in Swift
Crop images
PureLayout vs SnapKit - Great confrontation
Access iOS Camera, Photo Library, Video and File from User device
Promises for Swift & ObjC
ReactiveX for swift
VIPER and best practices
Dependency Injection
Singleton Pattern
Factory Pattern
Realm-swift
Cache Work with Swift 4 Codable
Configuration for different env
Organizing Your iOS Debug, Development, and Release States With .xcconfig Files
Pre-build script to alter your environment config
Optimizing-Swift-Build-Times
Cocoapods or Carthage
Pre-build (Swift) script to alter your Xcode project at build-time per environment and build configuration
function Super(){
this.val = 1;
this.arr = [];
}
function Sub(){
// ...
}
Sub.prototype = new Super();
var sub1 = new Sub();
var sub2 = new Sub();
sub1.val = 2;
sub1.arr.push(2);
alert(sub1.val); // 2
alert(sub2.val); // 1
alert(sub1.arr); // 2
alert(sub2.arr); // 2
优缺点
简单,易于实现
原型对象的引用属性是所有实例共享
执行sub1.arr.push(2);先对sub1进行属性查找,找遍了实例属性(在本例中没有实例属性),没找到,就开始顺着原型链向上找,拿到了sub1的原型对象,发现有arr属性。于是给arr末尾插入了2,所以sub2.arr也变了
创建子类实例时,无法向父类构造函数传参
function Super(val){
this.val = val;
this.arr = [];
this.say = function() { alert(1) }
}
function Sub(val){
Super.call(this, val);
// ...
}
var sub1 = new Sub(1);
var sub2 = new Sub(2);
sub1.arr.push(2);
alert(sub1.val); // 1
alert(sub2.val); // 2
alert(sub1.arr); // [2]
alert(sub2.arr); // []
alert(sub1.say === sub2.say); // false
优缺点
function Super(){
this.val = 1;
this.arr = [];
}
// 在此处声明函数
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
function Sub(){
Super.call(this);
// ...
}
Sub.prototype = new Super();
var sub1 = new Sub(1);
var sub2 = new Sub(2);
alert(sub1.fun === sub2.fun); // true
优缺点
function create(obj) {
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
this.val = 1;
this.arr = [1];
}
Super.prototype.fun1 = function(){};
Super.prototype.fun2 = function(){};
function Sub(){
Super.call(this);
// ...
}
var proto = create(Super.prototype);
proto.constructor = Sub;
Sub.prototype = proto;
var sub = new Sub();
alert(sub.val);
alert(sub.arr);
优缺点
function create(obj){
var F = function(){};
F.prototype = obj;
return new F();
}
function Super(){
this.val = 1;
this.arr = [];
}
// 拿到父类对象
var sup = new Super();
var sub = create(sup);
// 增强
sub.attr1 = 1;
sub.attr2 = 2;
//sub.attr3...
alert(sub.val); // 1
alert(sub.arr); // 1
alert(sub.attr1); // 1
优缺点
create
函数得到得到一个“纯洁”的新对象(“纯洁”是因为没有实例属性),再逐步增强之(填充实例属性), ES5提供了Object.create()函数,内部就是原型式继承You use the for-in loop to iterate over a sequence, such as items in an array, ranges of numbers, or characters in a string.
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!
You can also iterate over a dictionary to access its key-value pairs. Each item in the dictionary is returned as a (key, value) tuple when the dictionary is iterated, and you can decompose the (key, value) tuple’s members as explicitly named constants for use within the body of the for-in loop.
Dictionary are inherently unordered
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
print("\(animalName)s have \(legCount) legs")
}
// ants have 6 legs
// spiders have 8 legs
// cats have 4 legs
You can also use for-in loops with numeric ranges. This example prints the first few entries in a five-times table:
for index in 1...5 {
print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
If you don’t need each value from a sequence, you can ignore the values by using an underscore in place of a variable name
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// Prints "3 to the power of 10 is 59049"
use
stride(from:to:by:)
to determinate loop step
let minutes = 60
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
// render the tick mark every 5 minutes (0, 5, 10, 15 ... 45, 50, 55)
print(tickMark)
}
Methods are functions that are associated with a particular type
Instance methods
, which encapsulate specific tasks and functionality for working with an instance of a given type.Type methods
, which are associated with the type itself.
Instance methods
are functions that belong to instances of a particularclass
,structure
, orenumeration
. They support the functionality of those instances, either by providing ways to access and modify instance properties, or by providing functionality related to the instance’s purpose.An instance method has implicit access to all other instance methods and properties of that type
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
let counter = Counter()
// the initial counter value is 0
counter.increment()
// the counter's value is now 1
counter.increment(by: 5)
// the counter's value is now 6
counter.reset()
// the counter's value is now 0
Every instance of a type has an implicit property called self, which is exactly equivalent to the instance itself. You use the self property to refer to the current instance within its own instance methods.
//
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOf(x: Double) -> Bool {
// You use the self distinguish between the parameter name and the property name.
return self.x > x
}
}
Structures and enumerations are value types. By default, the properties of a value type cannot be modified from within its instance methods.
if you need to modify the properties of your structure or enumeration within a particular method, You can opt in to this behavior by placing the mutating keyword before the func keyword for that method
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
Mutating methods can assign an entirely new instance to the implicit self property.
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
enum TriStateSwitch {
case off, low, high
mutating func next() {
switch self {
case .off:
self = .low
case .low:
self = .high
case .high:
self = .off
}
}
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight is now equal to .high
ovenLight.next()
// ovenLight is now equal to .off
Methods that are called on the type itself. These kinds of methods are called type methods. You indicate type methods by writing the static keyword before the method’s func keyword.
Type methods are called with dot syntax, like instance methods. However, you call type methods on the type,
class SomeClass {
class func someTypeMethod() {
// type method implementation goes here
}
}
SomeClass.someTypeMethod()
Object.assign()
用来复制源对象的所有可枚举属性复制到目标对象中,并且返回目标对象
Object.assign(target, ...sources)
let target = {name: 'target'};
let source1 = {age: 23};
let source2 = {email: '[email protected]'};
// ...
// let sourceN ={.....};
traget = Object.assign(target, source1, source2);
注意:
target
中的属性具有相同的key
,那么它们将source
中的属性覆盖。较后的source
的属性也将将类似地覆盖先前的属性。Object.assign()
仅仅把target 中properties可 enumerable
的属性到target对象中通过指定的原型对象和属性,创建一个新的对象
Object.create(proto, [,. propertiesObject]);
实现原型继承
// Shape - superclass
function Shape() {
this.x = 0;
this.y = 0;
}
// superclass method
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - subclass
function Rectangle() {
Shape.call(this); // call super constructor.
}
// subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
var rect = new Rectangle();
console.log('Is rect an instance of Rectangle?', rect instanceof Rectangle);// true
console.log('Is rect an instance of Shape?', rect instanceof Shape);// true
rect.move(1, 1); // Outputs, 'Shape moved.'
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (继承而来)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因为 d 没有继承 Object.prototype
Object.freeze(obj)
将一个 object“冻住”:不能添加新的属性;不能删除现有的属性;不能修改现有属性,包括属性的 enumerability, configurability 和 writability。这个方法返回一个不可修改的对象,使用语法:
var obj = {
prop: function() {},
foo: 'bar'
};
// New properties may be added, existing properties may be changed or removed
obj.foo = 'baz';
obj.lumpy = 'woof';
delete obj.prop;
// Both the object being passed as well as the returned object will be frozen.
// It is unnecessary to save the returned object in order to freeze the original.
var o = Object.freeze(obj);
o === obj; // true
Object.isFrozen(obj); // === true
// Now any changes will fail
obj.foo = 'quux'; // silently does nothing
obj.quaxxor = 'the friendly duck'; // silently doesn't add the property
// ...and in strict mode such attempts will throw TypeErrors
function fail(){
'use strict';
obj.foo = 'sparky'; // throws a TypeError
delete obj.quaxxor; // throws a TypeError
obj.sparky = 'arf'; // throws a TypeError
}
fail();
// Attempted changes through Object.defineProperty will also throw
Object.defineProperty(obj, 'ohai', { value: 17 }); // throws a TypeError
Object.defineProperty(obj, 'foo', { value: 'eit' }); // throws a TypeError
深度 freeze
// To do so, we use this function.
function deepFreeze(obj) {
// Retrieve the property names defined on obj
var propNames = Object.getOwnPropertyNames(obj);
// Freeze properties before freezing self
propNames.forEach(function(name) {
var prop = obj[name];
// Freeze prop if it is an object
if (typeof prop == 'object' && prop !== null)
deepFreeze(prop);
});
// Freeze self (no-op if already frozen)
return Object.freeze(obj);
}
obj2 = {
internal: {}
};
deepFreeze(obj2);
obj2.internal.a = 'anotherValue';
obj2.internal.a; // undefined
Object.defineProperty(obj, prop, descriptor)
关于descriptor相关的属性
和两个存取器属性
var o = {}; // Creates a new object
// Example of an object property added with defineProperty with a data property descriptor
Object.defineProperty(o, 'a', {
value: 37,
writable: true,
enumerable: true,
configurable: true
});
// 'a' property exists in the o object and its value is 37
Object.defineProperties(obj, props)
Object.defineProperty
升级版本,可以一次同时定义多个属性,语法略有不同:
var obj = {};
Object.defineProperties(obj, {
'property1': {
value: true,
writable: true
},
'property2': {
value: 'Hello',
writable: false
}
// etc. etc.
});
Object.keys(obj)
以数组的形式返回对象中自身可枚举的属性
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']
// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']
// array like object with random key ordering
var an_obj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(an_obj)); // console: ['2', '7', '100']
// getFoo is property which isn't enumerable
var my_obj = Object.create({}, { getFoo: { value: function() { return this.foo; } } });
my_obj.foo = 1;
console.log(Object.keys(my_obj)); // console: ['foo']
CommonJS 和 AMD 是用于 JavaScript 模块管理的两大规范,前者定义的是模块的同步加载,主要用于 NodeJS ;而后者则是异步加载,通过 RequireJS 等工具适用于前端。随着 npm 成为主流的 JavaScript 组件发布平台,越来越多的前端项目也依赖于 npm 上的项目,或者自身就会发布到 npm 平台。因此,让前端项目更方便的使用 npm 上的资源成为一大需求。
web 开发中常用到的静态资源主要有 JavaScript、CSS、图片、Jade 等文件,webpack 中将静态资源文件称之为模块。 webpack 是一个 module bundler (模块打包工具),其可以兼容多种 js 书写规范,且可以处理模块间的依赖关系,具有更强大的 js 模块化的功能。Webpack 对它们进行统一的管理以及打包发布,其官方主页用下面这张图来说明 Webpack 的作用.
webpack 更 Gulp 的作用相同,是项目构建工具。
Gulp 出现的比较早,更适合于做任务型的,可以处理任何的网站静态网站、SPA、Node.js 项目代码,Gulp 里面就是一堆的任务;
Webpack 一般全部用来处理 SPA 应用,就 React、Vue.js、AngularJS 使用。
所以使用的场景不一样,因为内部的原理不同。
官网地址:http://webpack.github.io/docs/
对 CommonJS 、 AMD 、ES6 的语法做了兼容
对 js、css、图片等资源文件都支持打包
串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对 CoffeeScript、ES6的支持
有独立的配置文件 webpack.config.js
可以将代码切割成不同的 chunk,实现按需加载,降低了初始化时间
支持 SourceUrls 和 SourceMaps,易于调试
具有强大的 Plugin 接口,大多是内部插件,使用起来比较灵活
webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快
新建项目
在项目根目录下运行:
$ npm init -y
src 中的开发文件,dist 是打包后的文件
$ npm install webpack -g
$ npm install webpack -save-dev
$ npm install react -save
webpack.develop.config.js
// webpack 的开发配置文件
// 编写配置文件,要有最基本的文件入口和输出文件配置信息等
// 里面还可以加loader和各种插件配置使用
var path = require('path');
module.exports = {
// 单页面 SPA 的入口文件
entry:path.resolve(__dirname,'src/js/app.js'),
// 构建之后的文件输出位置配置
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
};
$ webpack --config webpack.develop.config.js
$ git init
$ git status
$ git add -A
$ git commit -m "项目目录结构及 webpack 初步配置"
把运行命令配置到 npm 的 script 中。 package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"develop": "webpack --config webpack.develop.config.js",
"publish": "webpack --config webpack.publish.config.js"
}
执行 :
$ npm run develop
如果需要一直输入 npm run develop 确实是一件非常无聊的事情,我们可以把让他安静的运行,让我们设置 webpack-dev-server
除了提供模块打包功能,Webpack 还提供了一个基于 Node.js Express 框架的开发服务器,它是一个静态资源 Web 服务器,对于简单静态页面或者仅依赖于独立服务的前端页面,都可以直接使用这个开发服务器进行开 发。在开发过程中,开发服务器会监听每一个文件的变化,进行实时打包,并且可以推送通知前端页面代码发生了变化,从而可以实现页面的自动刷新。
更好的方式实现自动启动:webpack 官方提供的一个第三个的插件,自动监听代码变化,帮我们重新构建,把 webpack 和 express 封装了
$ npm install webpack-dev-server -save-dev
调整 npm 的 package.json scripts 部分中开发命令的配置
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"develop": "webpack-dev-server --config webpack.develop.config.js --devtool eval --progress --colors --hot --content-base src",
"publish": "webpack --config webpack.publish.config.js"
}
webpack-dev-server - 在 localhost:8080 建立一个 Web 服务器
--devtool eval - 为你的代码创建源地址。当有任何报错的时候可以让你更加精确地定位到文件和行号
--progress - 显示合并代码进度
--colors -- hot,命令行中显示颜色!
--content-base 指向设置的输出目录//这点一定是我们的发布目录
在 src 下面,新建一个 index.html 文件,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webpack 使用</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="bundle.js"></script>
</html>
执行 npm run develop
之后我们发现执行没有结束,启动着监听,并在 8080 端口开启了一个服务器。
如果修改了 app.js 文件,会自动执行构建,刷新浏览器会发生变化。
在 index.html 访问的时候,会访问 bundle.js 文件,为什么,因为 webpack-dev-server 生成的 bundle 在内存中,放到内存中构建快
总的来说,当你运行 npm run develop 的时候,会启动一个 Web 服务器,然后监听文件修改,然后自动重新合并你的代码。真的非常简洁!
注意:
用 webpack-dev-server 生成 bundle.js 文件是在内存中的,并没有实际生成
如果引用的文件夹中已经有 bundle.js 就不会自动刷新了,你需要先把 bundle.js 文件手动删除
用 webstorm 需要注意,因为他是自动保存的,所以可能识别的比较慢,你需要手动的 ctrl+s 一下
修改 webpack.develop.config.js 的入口文件配置,修改 entry 部分如下:
var path = require('path');
module.exports = {
// 单页面 SPA 的入口文件
entry:[
// 实现浏览器自动刷新
'webpack/hot/dev-server',
'webpack-dev-server/client?http://localhost:8080',
path.resolve(__dirname,'src/js/app.js')
],
// 构建之后的文件输出位置配置
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
};
修改了配置文件,重新启动,执行 npm run develop
.
Loader:这是webpack准备的一些预处理工具
在构建项目之前做一些预处理操作,比如 ES6 转 ES5,Sass、Less
安装:
$ npm install babel-loader --save-dev
$ npm install babel-core babel-preset-es2015 babel-preset-react --save-dev
babel-loader: 转换器,编译 JSX 语法和 ES6 语法到 ES5 语法。
修改开发配置环境: webpack.develop.config.js
module: {
loaders: [
{
test: /\.jsx?$/, // 用正则来匹配文件路径,这段意思是匹配 js 或者 jsx
loader: 'babel', // 加载模块 "babel" 是 "babel-loader" 的缩写
query: {
presets: ['es2015', 'react']
}
}
]
}
一个 React Hello, World! app.js 文件
// 项目入口文件
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<div>
Hello World!
</div>,
document.getElementById('app')
);
webpack 允许像加载任何代码一样加载 CSS。可以选择需要的方式,但是可以为每个组件把所有的 CSS 加载到入口主文件中来做任何事情。
加载 CSS 需要 css-loader 和 style-loader,他们做两件不同的事情:
css-loader 会遍历 CSS 文件,然后找到 url() 表达式然后处理他们
style-loader 会把原来的 CSS 代码插入页面中的一个 style 标签中
$ npm install css-loader style-loader --save-dev
新建文件夹:components
新增:_base.css Hello.css Hello.js Hello.sass 文件
// 可以在 js 中引用 css 的加载器
{
test: /\.css$/,
loader: 'style!css' // 如果同时使用多个加载器,中间用 ! 连接,加载器的执行顺序是从右向左
}
!用来定义loader的串联关系,"-loader"是可以省略不写的,多个loader之间用“!”连接起来
1、在主入口文件中,比如 src/app.js 你可以为整个项目加载所有的 CSS
import './project-styles.css';
CSS 就完全包含在合并的应用中,再也不需要重新下载。
2、懒加载(推荐)
如果想发挥应用中多重入口文件的优势,可以在每个入口点包含各自的 CSS。
把模块用文件夹分离,每个文件夹有各自的 CSS 和 JavaScript 文件。
再次,当应用发布的时候,导入的 CSS 已经加载到每个入口文件中。
3、定制组件css
可以根据这个策略为每个组件创建 CSS 文件,可以让组件名和 CSS 中的 class 使用一个命名空间,来避免一个组件中的一些 class 干扰到另外一些组件的 class。如下图:
4、使用内联样式取代 CSS 文件
在 “React Native” 中不再需要使用任何 CSS 文件,只需要使用 style 属性,可以把你的 CSS 定义成一个对象,那样就可以根据项目重新来考略你的 CSS 策略。
下载依赖
$ npm install sass-loader -save-dev
修改配置文件
// 可以在 js 中引用 sass 的加载器
{
test: /\.scss$/,
loader: 'style!css!sass'
}
安装sass-loader之后运行运行 npm run develop
时报错
解决:
$ npm install node-sass -save-dev
直到 HTTP/2 才能在应用加载的时候避免设置太多 HTTP 请求。
根据浏览器不同必须设置并行请求数,如果在 CSS 中加载了太多图片的话,可以自动把这些图片转成 BASE64 字符串然后内联到 CSS 里来降低必要的请求数,这个方法取决于图片大小。
需要为应用平衡下载的大小和下载的数量,不过 Webpack 可以让这个平衡十分轻松适应。
下载载依赖
$ npm install url-loader file-loader --save-dev
修改配置文件:
{
test: /\.(png|jpg|gif|jpeg)$/,
loader: 'url?limit=25000'
},
// 处理字体
{
test: /\.(eot|woff|ttf|woff2|svg)$/,
loader: 'url?limit=25000'
}
加载器会把需要转换的路径变成 BASE64 字符串,在其他的 webpack 书中提到的这方面会把 CSS 中的 “url()” 像其他 require 或者 import 来处理。
意味着如果我们可以通过它来处理我们的图片文件。
url-loader 传入的 limit 参数是告诉它图片如果不大于 25KB 的话要自动在它从属的 css 文件中转成 BASE64 字符串。
大图片处理
在代码中是一下情况:
div.img {
background: url(../image/xxx.jpg)
}
//或者
var img = document.createElement("img");
img.src = require("../image/xxx.jpg");
document.body.appendChild(img);
// 可以这样配置
module: {
{
test: /\.(png|jpg)$/,
loader: 'url-loader?limit=10000&name=build/[name].[ext]'
}]
}
针对上面的两种使用方式,loader 可以自动识别并处理。根据 loader 中的设置,webpack 会将小于指点大小的文件转化成 base64 格式的 dataUrl,其他图片会做适当的压缩并存放在指定目录中。
"publish": " webpack --config webpack.publish.config.js -p",
指向生产的配置文件,并且加上了webpack的cli的-p,他会自动做一些优化
何时应该分离?
当应用依赖其他库尤其是像 React JS 这种大型库的时候,需要考虑把这些依赖分离出去,这样就能够让用户在更新应用之后不需要再次下载第三方文件。
当满足下面几个情况的时候你就需要这么做了:
1、当你的第三方的体积达到整个应用的 20% 或者更高的时候。
2、更新应用的时候只会更新很小的一部分
3、你没有那么关注初始加载时间,不过关注优化那些回访用户在你更新应用之后的体验。
4、有手机用户。
修改 webpack.publish.config.js 文件
var path = require('path');
var node_modules = path.resolve(__dirname, 'node_modules');
module.exports = {
entry: path.resolve(__dirname,'src/js/app.js'),
output: {
path: path.resolve(__dirname, 'build'),
filename: 'bundle.js',
},
// ...
plugins: [
new CleanPlugin(['dist']),
// 分离第三方应用插件,name属性会自动指向entry中vendros属性,filename属性中的文件会自动构建到output中的path属性下面
new webpack.optimize.CommonsChunkPlugin({name: 'vendors', filename: 'vendors.js'}),
// 可以新建多个抽离样式的文件,这样就可以有多个css文件了。
new ExtractTextPlugin("app.css"),
new webpack.DefinePlugin({
//去掉react中的警告,react会自己判断
'process.env': {
NODE_ENV: '"production"'
}
})
]
}
可以看到,其实生产环境的配置和开发的配置没有太大的不同,主要是把不需要的东西给去掉了
$ npm run publish
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webpack 使用</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="bundle.js"></script>
<script src="vendors.js"></script>
</html>
注意:记住要把这些文件都加入到你的 HTML 代码中,但在上面这种引入后,在浏览器打开之后报错,是因为引入顺序的问题
将上面 index.html 文件中的两个 js 文件引入顺序调换,如下
<!DOCTYPE html>
<!-- ... -->
<script src="vendors.js"></script>
<script src="bundle.js"></script>
</html>
// gulp 的任务是控制执行流程,webpack 的任务是处理复杂引用的依赖
var gulp = require('gulp');
// 删除文件和目录
var del = require('del');
// 按顺序执行
var gulpSequence = require('gulp-sequence');
// 引入webpack的本地模块
var webpack = require("webpack");
// 引入wbpack的配置文件
var webpackConfig = require("./webpack.publish.config.js");
gulp.task('default', ['sequence'], function() {
console.log("项目构建成功");
});
// 流程控制
gulp.task('sequence', gulpSequence('clean','webpack'));
// 删除文件和文件夹
gulp.task('clean', function(cb) {
//del('dist);// 如果直接给dist的目录,项目启动的顺序还有清除结果会报错,所以要写的更详细一些
del(['dist/js','dist/css','dist/img','dist/*.html']);
setTimeout(function(){
return cb();
},3000)
});
//写一个任务,在gulp中执行webpack的构建
// gulp 负责任务流程部分的操作,webpack 负责复杂模块系统的引用分离工作
gulp.task('webpack', function(cb) {
setTimeout(function(){
// 执行webpack的构建任务
webpack(webpackConfig, function (err, stats) {
if (err){
console.log("构建任务失败");
}else{
cb();
}
});
},3000)
});
一般情况下只有在下面的情况下才使用单入口模式:
1、应用很小
2、很少会更新应用
3、你不太关心初始加载时间
gulp + webpack 构建多页面前端项目
http://cnodejs.org/topic/56df76559386fbf86ddd6916
在配置文件中加入以下代码:
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
在 webpack 中编写 js 文件时,可以通过 require 的方式引入其他的静态资源,可通过 loade r对文件自动解析并打包文件。通常会将 js 文件打包合并,css文件会在页面的header中嵌入style的方式载入页面。但开发过程中我们并不想将样式打在脚本中,最好可以独立生成css文 件,以外链的形式加载。这时extract-text-webpack-plugin插件可以帮我们达到想要的效果。需要使用npm的方式加载插件,然后 参见下面的配置,就可以将js中的css文件提取,并以指定的文件名来进行加载。
$ npm install extract-text-webpack-plugin --save-dev
只能把 css 抽出来,但是 sass 的样式不能分离出来。
var ExtractTextPlugin = require("extract-text-webpack-plugin");
plugins: [
new ExtractTextPlugin("app.css")
]
html-webpack-plugin
var HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
template: './src/template.html',
htmlWebpackPlugin: {
"files": {
"css": ["app.css"],
"js": ["vendors.js", "bundle.js"]
}
},
// 压缩 html 文档
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
}
})
]
plugins: [
new webpack.DefinePlugin({
//去掉react中的警告,react会自己判断
'process.env': {
NODE_ENV: '"production"'
}
})
]
open-browser-webpack-plugin
https://github.com/baldore/open-browser-webpack-
webpack.develop.config.js
// 自动打开浏览器插件
var OpenBrowserPlugin = require('open-browser-webpack-plugin');
plugins: [
new OpenBrowserPlugin({url: 'http://localhost:8080/', browser: 'chrome'})
]
提取公共文件: CommonsChunkPlugin
plugins: [
// 分离第三方应用插件,name属性会自动指向 entry 中 vendros 属性,filename 属性中的文件会自动构建到 output 中的 path 属性下面
new webpack.optimize.CommonsChunkPlugin({name: 'vendors', filename: 'vendors.js'}),
]
自动添加引用插件,全局暴露插件,直接使用
clean-webpack-plugin
var CleanPlugin = require("clean-webpack-plugin");
plugins: [
new CleanPlugin(['dist']),
]
copy-webpack-plugin
webpack-config
https://github.com/mdreizin/webpack-config
安装:
$ npm install eslint -g
$ npm install eslint-loader -save-dev
module : {
preLoaders: [
{test: /\.js$/, loader: "eslint-loader", exclude: /node_modules/}
],
}
这个是重点要配合 chunkname 属性,react-router 的动态路由会用到
http://react-china.org/t/webpack-output-filename-output-chunkfilename/2256/2
基本上都是在 require.ensure 去加载模块的时候才会出现,chunkFileName,个人理解是 cmd 和 amd 异步加载而且没有给入口文件时,会生成了 no-name 的 chunk,所以 chunkFileName一般都会是 [id].[chunkhash].js, 也就是这种 chunk 的命名一般都会是 0.a5898fnub6.js.
webpack 在构建包的时候会按目录的进行文件的查找,resolve 属性中的 extensions 数组中用于配置程序可以自行补全哪些文件后缀:
resolve: {
//查找module的话从这里开始查找
root: '/pomy/github/flux-example/src', //绝对路径
//自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名
//注意一下, extensions 第一个是空字符串! 对应不需要后缀的情况.
extensions: ['', '.js', '.json', '.scss',’jsx’],
//模块别名定义,方便后续直接引用别名,无须多写长长的地址
alias: {
AppStore : 'js/stores/AppStores.js',//后续直接 require('AppStore') 即可
ActionType : 'js/actions/ActionType.js',
AppAction : 'js/actions/AppAction.js'
}
}
外部依赖不需要打包进 bundle,当我们想在项目中 require 一些其他的类库或者 API ,而又不想让这些类库的源码被构建到运行时文件中,这在实际开发中很有必要。
比如:在页面里通过 script 标签引用了 jQuery:<script src="//code.jquery.com/jquery-1.12.0.min.js"></script>
,所以并不想在其他 js 里再打包进入一遍,比如你的其他 js 代码类似:
其实就是不是通过require或者import引入的,而是直接写在html中的js地址。
// 配置了这个属性之后 react 和 react-dom 这些第三方的包都不会被构建进 js 中,那么我们就需要通过 cdn 进行文件的引用了
// 前边的这个名称是在项目中引用用的,相当于 import React from 'react1' 中的 react
externals: {
'react1': 'react',
'react-dom1': 'react-dom',
'$1': 'jQuery'
},
这样用了 externals 属性时不用分离插件了,作用是这里引的插件不会被 webpack 所打包。要么用 cdn 要么需要 webpack 打包。
http://fakefish.github.io/react-webpack-cookbook/Optimizing-rebundling.html
不使用就会把 react 再处理一遍
module.noParse 是 webpack 的另一个很有用的配置项,如果确定一个模块中没有其他新的依赖项就可以配置这个像,webpack 将不再扫描这个文件中的依赖。
module: {
noParse: [/moment-with-locales/]
}
http://fakefish.github.io/react-webpack-cookbook/Multiple-entry-points.html
http://fakefish.github.io/react-webpack-cookbook/Optimizing-caching.html
代码分离:
http://webpack.github.io/docs/code-splitting.html
(1)、在 react 中如何使用
http://fakefish.github.io/react-webpack-cookbook/Lazy-loaded-entry-points.html
(2)、在 react-router 中用到动态加载路由可以实现
Node 和webpack 集成用到的中间件:http://www.tuicool.com/articles/IvQb2ey
Node 和webpack 集成过程中遇到的坑如何解决:http://www.tuicool.com/articles/zEZneuq
不推荐用 webpack 构建 Node 代码
http://fakefish.github.io/react-webpack-cookbook/Hot-loading-components.html
Webpack允许像加载任何代码一样加载 CSS, 加载 CSS 需要 css-loader 和 style-loader,css-loader会遍历 CSS 文件,将文件里面的内容转化成CSS-JSON的形式,style-loader 让网页运行时根据require的顺序,依次把CSS-JSON字符串用 <style> </style>
的方式塞到 head
区域内
安装
npm install --save-dev css-loader style-loader
Name | Default | Description |
---|---|---|
root |
/ |
Path to resolve URLs, URLs starting with / will not be translated |
modules |
false |
Enable/Disable CSS Modules |
import |
true |
Enable/Disable @import handling |
url |
true |
Enable/Disable url() handling |
minimize |
false |
Enable/Disable minification |
sourceMap |
false |
Enable/Disable Sourcemaps |
camelCase |
false |
Export Classnames in CamelCase |
importLoaders |
0 |
Number of loaders applied before CSS loader |
默认情况下CSS 文件中的 @import
和 url()
都会被解析成像 import
那样,所以需要file-loader
和 url-loader
配合使用
//没开启 CSS Module下
`url(image.png)` => `require('./image.png')`
`url(~module/image.png)` => `require('module/image.png')`
根据你的应用,你可能会考略三种策略。另外,你需要考虑把一些基础的 CSS 内联到初始容器中(index.html),这样设置的结构能够在应用下载和执行的时候加载剩下的应用。
在你的主入口文件中个,比如 app/main.js 你可以为整个项目加载所有的 CSS:
#app/main.js
import './project-styles.css';
// 其他 JS 代码
如果应用中有多个入口文件的,你可以在每个入口点包含各自的 CSS:
#app/main.js
import './style.css';
// 其他 JS 代码
#app/entryA/main.js
import './style.css';
// 其他 JS 代码
#app/entryB/main.js
import './style.css';
// 其他 JS 代码
你可以根据这个策略为每个组件创建 CSS 文件,可以让组件名和 CSS 中的 class 使用一个命名空间,来避免一个组件中的一些 class 干扰到另外一些组件的 class。
#app/components/MyComponent.css
.MyComponent-wrapper {
background-color: #EEE;
}
#app/components/MyComponent.jsx
import './MyComponent.css';
import React from 'react';
export default React.createClass({
render: function () {
return (
<div className="MyComponent-wrapper">
<h1>Hello world</h1>
</div>
)
}
});
import React from 'react';
var style = {
backgroundColor: '#EEE'
};
export default React.createClass({
render: function () {
return (
<div style={style}>
<h1>Hello world</h1>
</div>
)
}
});
前面说的, css-loader
会将css中遇到的 url('./xx.jpg')
转成 require('./xx.jpg')
的形式; file-loader
的作用就是处理文件加载。
修改文件名,放在输出目录下,并返其对应的 url .
默认修改后的文件名,是文件内容的MD5哈希串。你也可以自定义文件名。比如:
require(“file?name=js/[hash].script.[ext]!./javascript.js”); // => js/0dcbbaa701328a3c262cfd45869e351f.script.js
require(“file?name=html-[hash:6].html!./page.html”); // => html-109fa8.html
require(“file?name=[hash]!./flash.txt”); // => c31e9820c001c9c4a86bce33ce43b679
require(“file?name=[sha512:hash:base64:7].[ext]!./image.png”); // => gdyb21L.png // use sha512 hash instead of md5 and with only 7 chars of base64
require(“file?name=img-[sha512:hash:base64:7].[ext]!./image.jpg”); // => img-VqzT5ZC.jpg // use custom name, sha512 hash instead of md5 and with only 7 chars of base64
require(“file?name=picture.png!./myself.png”); // => picture.png
require(“file?name=[path][name].[ext]?[hash]!./dir/file.png”) // => dir/file.png?e43b20c069c4a01867c31e98cbce33c9
这个加载器的工作方式很像file-loader
。只是当文件大小小于限制值时,它可以返回一个Data Url。限制值可以作为查询参数传入。默认不限制。比如:
require(“url?limit=10000!./file.png”); // => 如果”file.png”小于10kb,则转成一个DataUrl
require(“url?mimetype=image/png!./file.png”); // => 指定文件的mimetype (不指定,则根据文件后缀推测.)
user.should.have.property('name', 'test');
user.enabled.should.ok;
expect(5).to.be.a('number');
expect(res.body.data).to.be.deep.equal({});
存在的问题
Power Assert in JavaScript.
Provides descriptive assertion messages through standard assert interface.
No API is the best API.
https://github.com/power-assert-js/power-assert
和常用的断言库相比,它的优点是
const assert = require('power-assert');
describe('test/showcase.test.js', () => {
const arr = [ 1, 2, 3 ];
it('power-assert', () => {
assert(arr[1] === 10);
});
});
// output:
4) test/showcase.test.js power-assert:
AssertionError: # test/showcase.test.js:6
assert(arr[1] === 10)
| | |
| 2 false
[1,2,3]
[number] 10
=> 10
[number] arr[1]
=> 2
var assert = require('assert');
describe('Array', function(){
beforeEach(function(){
this.ary = [1,2,3];
});
describe('#indexOf()', function(){
it('should return index when the value is present', function(){
var zero = 0, two = 2;
assert(this.ary.indexOf(zero) === two);
});
it('should return -1 when the value is not present', function(){
var minusOne = -1, two = 2;
assert.ok(this.ary.indexOf(two) === minusOne, 'THIS IS AN ASSERTION MESSAGE');
});
});
});
describe('various types', function(){
function Person(name, age) {
this.name = name;
this.age = age;
}
beforeEach(function(){
this.types = [
'string', 98.6, true, false, null, undefined,
['nested', 'array'],
{object: true},
NaN, Infinity,
/^not/,
new Person('alice', 3)
];
});
it('demo', function(){
var index = this.types.length -1,
bob = new Person('bob', 5);
assert(this.types[index].name === bob.name);
});
});
使用 babel-plugin-espower
插件
npm install --save-dev babel-plugin-espower
.babelrc
文件{
"presets": [
...
],
"plugins": [
"babel-plugin-espower"
]
}
babel-register
编译文件$(npm bin)/mocha --require babel-register test/some_test.js
本文主要记录通过 隧道机器人框架 EH Forwarder Bot
,
实现了通过 Telegram Bot收发微信功能,无论文字、语音、图片还是视频、表情,都可以互通.
Docker 下部署 EFB 框架
EFB 的搭建有一些门槛,主要的问题是 Python 3 的配置问题,新手难免碰了不少坑。
但用上Docker环境部署,EFB 的搭建过程就非常方便。
系统为 Ubuntu 16.04 x64
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.12.9-041209-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Welcome to Alibaba Cloud Elastic Compute Service !
docker-ce
具体官方教程 Ubuntu 下安装docker
sudo apt-get -y install apt-transport-https ca-certificates curl
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get -y install docker-ce
镜像地址 docker-EFB
docker pull royx/docker-efb
EFB 目前实现的 Telegram 与 微信 互通的流程:
Telegram bot > EFB > 微信网页版 > 微信
先配置 Telegram bot
botfather
并和他对话@botfather
说话,输入 /newbot
Token
设置 bot 隐私权限
继续与 @botfather
对话,输入 /setprivacy
,选择刚刚创建的机器人,点击 Disable
获得 Telegram ID
再和另外一个机器人 @get_id_bot 对话,点击 start 即可获得你的 Telegram ID,一串数字(Chat ID)。
至此,Telegram
的配置完成,我们得到两个重要的数字:token
、 Telegram ID
新建一份配置文件 vim config.py
master_channel = 'plugins.eh_telegram_master', 'TelegramChannel'
slave_channels = [('plugins.eh_wechat_slave', 'WeChatChannel')]
eh_telegram_master = {
"token": "12345678:QWFPGJLUYarstdheioZXCVBKM",
"admins": [13456782],
"bing_speech_api": ["xxx", "xxx"],
"baidu_speech_api": {
"app_id": 0,
"api_key": "xxx",
"secret_key": "xxx"
}
}
注意把上面 token
以及 admins
冒号后面的部分替换成刚刚获得的 token
和Chat ID
。
新建一个 tgdata.db 文件
touch tgdata.db
简单启动容器
$ docker run -d --restart=always \
--name=ehforwarderbot \
-v $(pwd)/config.py:/opt/ehForwarderBot/config.py \
-v $(pwd)/tgdata.db:/opt/ehForwarderBot/plugins/eh_telegram_master/tgdata.db \
royx/docker-efb
另外推荐用docker-compose 启动和管理容器
sudo curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
创建docker-compoes.yml
文件, config.py
以及 tgdata.db
放在同一个文件夹
ehforwarderbot:
image: royx/docker-efb:v1.6.4
container_name: ehforwarderbot
volumes:
- ./config.py:/opt/ehForwarderBot/config.py
- ./tgdata.db:/opt/ehForwarderBot/plugins/eh_telegram_master/tgdata.db
在该目录下执行 docker-compose up -d
通过 docker-compose logs -f
可以查看日志里输出的二维码之类的信息(CTRL + C 退出)
更新的话修改docker-compose.yml
文件之后
重新执行 docker-compose up -d
即可
登录微信
现在,我们只需要登录微信,就可以了:
docker logs ehforwarderbot
在屏幕上,你会看到一个二维码,用微信扫描,登录即可。
在webpack中 可以对组件进行hot reload
但对于一些样式文件,如sass,less , 虽然是已经rebuild了,
但前端依旧没更新。
问题可以在前端入口加上相应的hook
const hotEmitter = require('webpack/hot/emitter');
hotEmitter.on('webpackHotUpdate', () => {
document.querySelectorAll('link[href][rel=stylesheet]').forEach((link) => {
const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`);
const newLink = link.cloneNode();
newLink.href = nextStyleHref;
link.parentNode.appendChild(newLink);
setTimeout(() => {
link.parentNode.removeChild(link);
}, 2000);
});
})
性能优化总是会有成本,但并不总是带来好处。
下面谈谈 useMemo 和 useCallback 的成本和收益。
结合下面的例子来讲讲
function CandyDispenser() {
const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
const [candies, setCandies] = React.useState(initialCandies)
const dispense = candy => {
setCandies(allCandies => allCandies.filter(c => c !== candy))
}
return (
<div>
<h1>Candy Dispenser</h1>
<div>
<div>Available Candy</div>
{candies.length === 0 ? (
<button onClick={() => setCandies(initialCandies)}>refill</button>
) : (
<ul>
{candies.map(candy => (
<li key={candy}>
<button onClick={() => dispense(candy)}>grab</button> {candy}
</li>
))}
</ul>
)}
</div>
</div>
)
}
当被问到如何优化以上代码时,
很多人会想到用 React.useCallback 里包裹 dispense 函数
const dispense = React.useCallback(candy => {
setCandies(allCandies => allCandies.filter(c => c !== candy))
}, [])
但实际情况是 使用原来的代码性能会更好,
如果你选择的是 useCallback,再好好思考下。
为什么使用了useCallback 后性能反而性能不如预期呢?!
很多情况使用 React.useCallback 可以用来提高性能,并且“内联函数可能会对性能造成问题”,那么为啥不使用 usecallCallback变得更好的。useCallback版本做了更多的工作之外,它们完全相同。 我们不仅需要定义函数,还要定义一个数组([])并调用 React.useCallback,它本身会设置属性和运行逻辑表达式等。
在组件的第二次渲染中,原来的 dispense 函数被垃圾收集(释放内存空间),然后创建一个新的 dispense 函数。 但是使用 useCallback 时,原来的 dispense 函数不会被垃圾收集,并且会创建一个新的 dispense 函数,所以从内存的角度来看,这会变得更糟。
作为一个相关的说明,如果你有其它依赖,那么React很可能会挂起对前面函数的引用,因为 memoization 通常意味着我们保留旧值的副本,以便在我们获得与先前给出的相同依赖的情况下返回。 特别聪明的你会注意到,这意味着React还必须挂在对这个等式检查依赖项的引用上
useMemo 类似于 useCallback,除了它允许你将 memoization 应用于任何值类型(不仅仅是函数)。 它通过接受一个返回值的函数来实现这一点,然后只在需要检索值时调用该函数(通常这只有在每次渲染中依赖项数组中的元素发生变化时才会发生一次)
如果不想在每次渲染时初始化那个 initialCandies 数组, 可以用 useMemo 进行调整
- const initialCandies = ['snickers', 'skittles', 'twix', 'milky way']
+ const initialCandies = React.useMemo(
+ () => ['snickers', 'skittles', 'twix', 'milky way'],
+ [],
+ )
可以避免那个问题,但是节省的成本是如此之小,以至于换来使代码更加复杂的成本是不值得的。实际上,这里使用useMemo 也可能会更糟,因为我们再次进行了函数调用,并且代码会执行属性赋值等。
这两个 hooks 内置于 React 都有特别的原因:
10.11 El Capitan中,需要拷貝override文件的地址變更為 /System/Library/Displays/Contents/Resources/Overrides
但是由於蘋果提升了系統安全性,用戶無法將文件拷貝入此文件夾(會出現禁止符號,沒有密碼提示)。
这是我之前的问题:
我的Mac由于工作需要,接入了一台超宽屏幕的外置显示器,分辨率是1560*1080.用的是Mini Display Port转Display Port接口,结果有一个问题。
什么都还好,就是字体十分发虚!网页上的字体还勉强,打开iwork编辑,字体只要小于12号根本没法看。
结果进入系统信息,发现这个显示器被mac识别为电视了!请看下图。
最后一项TV是yes。
请问如何让mac把它识别成显示器不是电视从而改善字体发虚的问题啊!网上的帖子都试过了无效,谢谢各路大神了!
解决方法:
这是因为Mac在识别显示器上不够智能,经常把显示器识别为电视,所以导致采用电视的渲染方式,使得字体模糊发虚。
所以我们要对显示器强制RGB渲染。
0.下載這個到你的Downloads文件夾 patch-edid.rb (1.49 KB, 下载次数: 1898)
1,打开终端,输入
cd Downloads
ruby patch-edid.rb
(这是一个命令,不要拆成两行来输入)
2,打开“下载”目录,你会发现多了一个文件夹(以我的为例:DisplayVendorID-5e3)
3,进入Finder以下目录:
/System/Library/Displays/Overrides
4,把那个文件夹拷贝进去(需要输入密码确认,如果有同名文件夹建议覆盖前先备份)
5,重新链接显示器。
你会惊奇的发现,显示效果好了100倍!
感謝11頁的swfes朋友提供適用於OSX 10.11 EL Capitan中的方法
10.11找到解决办法了!
Closures can capture and store references to any constants and variables from the context in which they are defined.
Global functions
are closures that have a name and do not capture any values.Nested functions
are closures that have a name and can capture values from their enclosing functionClosure expressions
are unnamed closures written in a lightweight syntax that can capture values from their surrounding context.General Form of Closure
{ (parameters) -> return type in
statements
}
A trailing closure is written after the function call’s parentheses, even though it is still an argument to the function. When you use the trailing closure syntax, you don’t write the argument label for the closure as part of the function call.
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
}
// Here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure(closure: {
// closure's body goes here
})
// Here's how you call this function with a trailing closure instead:
someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
// with closure systax
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
// shorten with single line version
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
// shorten with Inferring Type From Context version
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
// shorten with Implicit Returns from Single-Expression
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
// shorten with Shorthand Argument Names
reversedNames = names.sorted(by: { $0 > $1 } )
// shorten with Operator Methods
reversedNames = names.sorted(by: >)
// shorten with Trailing Closures
reversedNames = names.sorted() { $0 > $1 }
reversedNames = names.sorted { $0 > $1 } // only argument can omit parentheses()
A closure can capture constants and variables from the surrounding context in which it is defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.
In swift, A nested function can capture any of its outer function’s arguments and can also capture any constants and variables defined within the outer function.
@escaping
closure means it needs to refer to self explicitly
func makeAnIncrement(_ delta: Int) -> () -> Int {
var startValue: Int = 0;
func Inc() -> Int {
startValue += delta;
return startValue;
}
return Inc
}
let Ten = makeAnIncrement(10);
_ = Ten();
_ = Ten();
let t = Ten();
print(t) // 30
let Twn = makeAnIncrement(20);
_ = Twn();
_ = Twn();
let x = Twn();
print(x) // 60
Whenever you assign a function or a closure to a constant or a variable, you are actually setting that constant or variable to be a reference to the function or closure.
A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns. When you declare a function that takes a closure as one of its parameters, you can write
@escaping
before the parameter’s type to indicate that the closure is allowed to escape.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
completionHandlers.first?()
print(instance.x)
// Prints "100"
An
autoclosure
is a closure that is automatically created to wrap an expression that’s being passed as an argument to a function. It doesn’t take any arguments, and when it’s called, it returns the value of the expression that’s wrapped inside of it. This syntactic convenience lets you omit braces around a function’s parameter by writing a normal expression instead of an explicit closure.
An autoclosure lets you delay evaluation, because the code inside isn’t run until you call the closure
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"
// autoclosure version
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"
开发electron客户端程序,打包是绕不开的问题。下面就我在工作中的经验以及目前对electron-builder
的了解来分享一些心得。
官网的定义
A complete solution to package and build a ready for distribution Electron app for macOS, Windows and Linux with “auto update” support out of the box.
关于electron
和electron-builder
的基础部分这篇文章就跳过了,有兴趣的话可以看这篇文章
builder的使用和配置都是很简单的
builder配置有两种方式
package.json
中直接配置使用(比较常用,我们下面着重来讲这个)electron-builder.yml
文件demo地址会在文章末尾给出(demo项目中electron
使用得是V2.0.7
版本,目前更高得是2.0.8
版本)。
下面是一个简单的package.js
中带注释的配置
"build": { // 这里是electron-builder的配置
"productName":"xxxx",//项目名 这也是生成的exe文件的前缀名
"appId": "com.xxx.xxxxx",//包名
"copyright":"xxxx",//版权 信息
"directories": { // 输出文件夹
"output": "build"
},
// windows相关的配置
"win": {
"icon": "xxx/icon.ico"//图标路径
}
}
在配置文件中加入以上的文件之后就可以打包出来简单的文件夹,文件夹肯定不是我们想要的东西。下一步我们来继续讲别的配置。
2. 打包目标配置
要打包成安装程序的话我们有两种方式,
"win": { // 更改build下选项
"icon": "build/icons/aims.ico",
"target": [
{
"target": "nsis" // 我们要的目标安装包
}
]
},
"dmg": { // macOSdmg
"contents": [
...
]
},
"mac": { // mac
"icon": "build/icons/icon.icns"
},
"linux": { // linux
"icon": "build/icons"
}
这个要详细的讲一下,这个nsis的配置指的是安装过程的配置,其实还是很重要的,如果不配置nsis那么应用程序就会自动的安装在C盘。没有用户选择的余地,这样肯定是不行的
关于nsis的配置是在build中nsis这个选项中进行配置,下面是部分nsis配置
"nsis": {
"oneClick": false, // 是否一键安装
"allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
"allowToChangeInstallationDirectory": true, // 允许修改安装目录
"installerIcon": "./build/icons/aaa.ico",// 安装图标
"uninstallerIcon": "./build/icons/bbb.ico",//卸载图标
"installerHeaderIcon": "./build/icons/aaa.ico", // 安装时头部图标
"createDesktopShortcut": true, // 创建桌面图标
"createStartMenuShortcut": true,// 创建开始菜单图标
"shortcutName": "xxxx", // 图标名称
"include": "build/script/installer.nsh", // 包含的自定义nsis脚本 这个对于构建需求严格得安装过程相当有用。
},
关于include
和 script
到底选择哪一个 ?
在对个性化安装过程需求并不复杂,只是需要修改一下安装位置,卸载提示等等的简单操作建议使用include
配置,如果你需要炫酷的安装过程,建议使用script
进行完全自定义。
NSIS
对于处理安装包这种东西,功能非常的强大。但是学习起来并不比一门高级语言要容易。其中的奥秘还要各位大佬自行探索
这里上一些学习资源
主要是windows中64和32位的配置
CLI参数
electron-builder --ia32 // 32位
electron-builder // 64位(默认)
nsis中配置
"win": {
"icon": "build/icons/aims.ico",
"target": [
{
"target": "nsis",
"arch": [ // 这个意思是打出来32 bit + 64 bit的包,但是要注意:这样打包出来的安装包体积比较大,所以建议直接打32的安装包。
"x64",
"ia32"
]
}
]
}
下面这个是给更新用的配置,主要是为了生成lastest.yaml
配置文件
"publish": [
{
"provider": "generic", // 服务器提供商 也可以是GitHub等等
"url": "http://xxxxx/" // 服务器地址
}
],
基本上可用的完整的配置
"build": {
"productName":"xxxx",//项目名 这也是生成的exe文件的前缀名
"appId": "com.leon.xxxxx",//包名
"copyright":"xxxx",//版权 信息
"directories": { // 输出文件夹
"output": "build"
},
"nsis": {
"oneClick": false, // 是否一键安装
"allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
"allowToChangeInstallationDirectory": true, // 允许修改安装目录
"installerIcon": "./build/icons/aaa.ico",// 安装图标
"uninstallerIcon": "./build/icons/bbb.ico",//卸载图标
"installerHeaderIcon": "./build/icons/aaa.ico", // 安装时头部图标
"createDesktopShortcut": true, // 创建桌面图标
"createStartMenuShortcut": true,// 创建开始菜单图标
"shortcutName": "xxxx", // 图标名称
"include": "build/script/installer.nsh", // 包含的自定义nsis脚本
},
"publish": [
{
"provider": "generic", // 服务器提供商 也可以是GitHub等等
"url": "http://xxxxx/" // 服务器地址
}
],
"files": [
"dist/electron/**/*"
],
"dmg": {
"contents": [
{
"x": 410,
"y": 150,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 150,
"type": "file"
}
]
},
"mac": {
"icon": "build/icons/icon.icns"
},
"win": {
"icon": "build/icons/aims.ico",
"target": [
{
"target": "nsis",
"arch": [
"ia32"
]
}
]
},
"linux": {
"icon": "build/icons"
}
}
Commands(命令):
electron-builder build 构建命名 [default]
electron-builder install-app-deps 下载app依赖
electron-builder node-gyp-rebuild 重建自己的本机代码
electron-builder create-self-signed-cert 为Windows应用程序创建自签名代码签名证书
electron-builder start 使用electronic-webpack在开发模式下运行应用程序(须臾要electron-webpack模块支持)
Building(构建参数):
--mac, -m, -o, --macos Build for macOS, [array]
--linux, -l Build for Linux [array]
--win, -w, --windows Build for Windows [array]
--x64 Build for x64 (64位安装包) [boolean]
--ia32 Build for ia32(32位安装包) [boolean]
--armv7l Build for armv7l [boolean]
--arm64 Build for arm64 [boolean]
--dir Build unpacked dir. Useful to test. [boolean]
--prepackaged, --pd 预打包应用程序的路径(以可分发的格式打包)
--projectDir, --project 项目目录的路径。 默认为当前工作目录。
--config, -c 配置文件路径。 默认为`electron-builder.yml`(或`js`,或`js5`)
Publishing(发布):
--publish, -p 发布到GitHub Releases [choices: "onTag", "onTagOrDraft", "always", "never", undefined]
Deprecated(废弃):
--draft 请改为在GitHub发布选项中设置releaseType [boolean]
--prerelease 请改为在GitHub发布选项中设置releaseType [boolean]
--platform 目标平台 (请更改为选项 --mac, --win or --linux)
[choices: "mac", "win", "linux", "darwin", "win32", "all", undefined]
--arch 目标arch (请更改为选项 --x64 or --ia32)
[choices: "ia32", "x64", "armv7l", "arm64", "all", undefined]
Other(其他):
--help Show help [boolean]
--version Show version number [boolean]
Examples(例子):
electron-builder -mwl 为macOS,Windows和Linux构建(同时构建)
electron-builder --linux deb tar.xz 为Linux构建deb和tar.xz
electron-builder -c.extraMetadata.foo=bar 将package.js属性`foo`设置为`bar`
electron-builder --config.nsis.unicode=false 为NSIS配置unicode选项
TargetConfiguration(构建目标配置):
target: String - 目标名称,例如snap.
arch “x64” | “ia32” | “armv7l” | “arm64”> | “x64” | “ia32” | “armv7l” | “arm64” -arch支持列表
NPM
下载的问题
因为NPM
在国内比较慢。导致electron-V.xxxx.zip
下载失败。这些东西如果是第一次打包的话是需要下载对应electron
版本的支持文件。解决办法有两个
.npmrc
文件。然后加入下面这句代码,但是这个有时候也不是很好用ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/
C:\Users\Administrator\AppData\Local\electron\Cache
。例如我要下载1.8.4版本的electron
,那么找到镜像下得文件然后放到指定文件夹中。(如果是在执行npm install
时下载不下来)直接在淘宝镜像下载对应版本的zip,然后扔到C:\Users\YourUserName.electron就行
这就解决了这个问题,简单又暴力。
NSIS
下载问题
如果你要打NSIS
得包还需要西再下载nsis-resources-xxx
等等包。经过上面得经验这下我们知道缺什么就填什么呗,通过错误日志我们可以得到我们要下载得版本,一般错误中通常会展示github
下载地址,直接点开连接去下载。但是位置这次不一样了。因为这是electron-builder
的支持环境所以我们要放在C:\Users\Administrator\AppData\Local\electron-builder\cache\nsis\
下了。
electron-builder
是一个简单又强大的库。解决了打包这个棘手的问题,而且可以应对大部分的打包需求。
koa
是由 TJ 利用 generator 开发的 web 框架。最近在写 koa的时候,顺便深入去了解 generator 与 co。
function* gen(){
yield 1;
yield 2;
}
var g = gen();
console.log(g.next());//{ value: 1, done: false }
console.log(g.next());//{ value: 2, done: false }
console.log(g.next());//{ value: undefined, done: true }
console.log(g.next());//Error: Generator has already finished
上面我们可以看到
Generator
不同于普通函数,是可以暂停执行的,所以函数名之前要加星号作为区别。Generator
被调用后会返回一个内部指针(即遍历器 )g 。这是 Generator
函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针 g 的 next
方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield
next
函数,调用 next 会返回 yield运算的结果对象,并停止。再次调用会在下一个 yield 处停止,当所有的 yield
被执行完,调用 next
函数会返回 { value: undefined, done: true }
。再次调用会报错数据交换
function* gen(x){
var y = yield x + 2;
return y;
}
var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: true }
第一个 next 方法的 value 属性,返回表达式 x + 2 的值(3)。
第二个 next 方法带有参数2,这个参数可以传入 Generator 函数,
作为上个阶段异步任务的返回结果,被函数体内的变量 y 接收。
因此,这一步的 value 属性,返回的就是2(变量 y 的值)。
Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。
function* gen(x){
try {
var y = yield x + 2;
} catch (e){
console.log(e);
}
return y;
}
var g = gen(1);
g.next();
g.throw('出错了');
// 出错了
使用指针对象的 throw 方法抛出的错误,可以被函数体内的 try ... catch 代码块捕获。
利用
Generator
以上这些特点可对异步的代码进行改写,使得更像同步一样便于理解
var fetch = require('node-fetch');
function* gen(){
var url1 = 'https://api.github.com/users/repo';
var url2 = 'https://api.github.com/users/issue';
var repo = yield fetch(url1); // promise
var issue = yield fetch(url2);
console.log(repo, issue);
}
function run(GenFunc) {
return function() {
var args = Array.slice.call(arguments)
var gen = GenFunc()
next()
function next(args) { // 传入args
if (gen.next) {
var ret = gen.next(args) // 使用args
if (!ret.done) {
ret.value(next)
}
}
}
}
}
run(gen);
从上面的代码可以看出, generator函数需要连续运行下去,需要做到以下几点
co的API一般用法,
Returns a promise that resolves a generator, generator function, or any function that returns a generator.
co(fn*).then( )
co(function* () {
return yield Promise.resolve(true);
}).then(function (val) {
console.log(val);
}, function (err) {
console.error(err.stack);
});
co核心代码不多,设计的非常精妙
function co(gen) {
var ctx = this;
var args = slice.call(arguments, 1);
// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.apply(ctx, args);
if (!gen || typeof gen.next !== 'function') return resolve(gen);
//主要就是走下面的onFulfilled方法,这个方法返回的是一个promise(resolve或者reject)
onFulfilled();
/**
* @param {Mixed} res
* @return {Promise}
* @api private
*/
function onFulfilled(res) {
var ret;
try {
//调用第一次next方法
ret = gen.next(res);
} catch (e) {
//出错了直接reject出去
return reject(e);
}
//将第一次的结果({done:true,value:{}}) 传入内部方法next
next(ret);
return null;
}
/**
* @param {Error} err
* @return {Promise}
* @api private
*/
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* Get the next value in the generator,
* return a promise.
*
* @param {Object} ret
* @return {Promise}
* @api private
*/
function next(ret) {
//如果done为true的话,代表执行结束,返回一个resolve的promise
if (ret.done) return resolve(ret.value);
//还没执行完,就将ret.value转换成一个promise
var value = toPromise.call(ctx, ret.value);
//如果成功转化为了promise,就在这个promise执行完了再调用onFulfilled方法
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}
export enum EnvironmentType {
development = "development",
staging = "staging",
production = "production"
}
export interface EnvironmentConfig {
server: string
port: number
}
type EnvironmentConfigMap = Record<EnvironmentType, EnvironmentConfig>
type FlattenIfArray<T> = T extends (infer R)[] ? R : T
Check if our generic Type is the array, If it is array extract the real type from it,
If it does not leave it as is
java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
brew install gradle
> gradle -v
------------------------------------------------------------
Gradle 6.6.1
------------------------------------------------------------
Build time: 2020-08-25 16:29:12 UTC
Revision: f2d1fb54a951d8b11d25748e4711bec8d128d7e3
Kotlin: 1.3.72
Groovy: 2.5.12
Ant: Apache Ant(TM) version 1.10.8 compiled on May 10 2020
JVM: 14.0.1 (Oracle Corporation 14.0.1+14)
OS: Mac OS X 10.14.6 x86_64
create a folder for the new project and change directory into it.
$ mkdir demo
$ cd demo
$ gradle init
Select type of project to generate:
1: basic
2: application
3: library
4: Gradle plugin
Enter selection (default: basic) [1..4] 2
Select implementation language:
1: C++
2: Groovy
3: Java
4: Kotlin
5: Scala
6: Swift
Enter selection (default: Java) [1..6] 3
Select build script DSL:
1: Groovy
2: Kotlin
Enter selection (default: Groovy) [1..2] 1
Select test framework:
1: JUnit 4
2: TestNG
3: Spock
4: JUnit Jupiter
Enter selection (default: JUnit 4) [1..4]
Project name (default: demo):
Source package (default: demo):
BUILD SUCCESSFUL
2 actionable tasks: 2 executed
project structure
├── gradle // Generated folder for wrapper files
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew // Gradle wrapper start scripts
├── gradlew.bat
├── settings.gradle // Settings file to define build name and subprojects
└── app
├── build.gradle // Build script of app project
└── src
├── main
│ └── java // Default Java source folder
│ └── demo
│ └── App.java
└── test // Default Java test source folder
└── java
└── demo
└── AppTest.java
Classes and structures are general-purpose, flexible constructs that become the building blocks of your program’s code. You define properties and methods to add functionality to your classes and structures by using exactly the same syntax as for constants, variables, functions.
Classes and structures things in common.
Define properties to store values
Define methods to provide functionality
Define subscripts to provide access to their values using subscript syntax
Define initializers to set up their initial state
Be extended to expand their functionality beyond a default implementation
Conform to protocols to provide standard functionality of a certain kind
Classes have additional capabilities that structures do not:
Inheritance enables one class to inherit the characteristics of another.
Type casting enables you to check and interpret the type of a class instance at runtime.
Deinitializers enable an instance of a class to free up any resources it has assigned.
Reference counting allows more than one reference to a class instance.
class SomeClass {
// class definition goes here
}
struct SomeStructure {
// structure definition goes here
}
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
Syntax for creating instances is very similar for both structures and classes:
let someResolution = Resolution()
let someVideoMode = VideoMode()
Access the properties of an instance using
dot
syntax. In dot syntax, you write the property name immediately after the instance name, separated by a period (.), without any spaces:
print("The width of someResolution is \(someResolution.width)")
// Prints "The width of someResolution is 0"
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Prints "The width of someVideoMode is now 1280"
All structures have an automatically-generated memberwise initializer, which you can use to initialize the member properties of new structure instances. Initial values for the properties of the new instance can be passed to the memberwise initializer by name:
let vga = Resolution(width: 640, height: 480)
Class instances do not receive a default memberwise initializer.
value type
is a type whose value is copied when it is assigned to a variable or constant, or when it is passed to a function.
Any structure and enumeration instances you create—and any value types they have as properties—are always copied when they are passed around in your code.
Reference types
are not copied when they are assigned to a variable or constant, or when they are passed to a function. Rather than a copy, a reference to the same existing instance is used instead.
Swift provides two identity operators:
Identical to (===)
Not identical to (!==)
Identical to
means that two constants or variables of class type refer to exactly the same class instance.
Equal to
means that two instances are considered “equal” or “equivalent” in value, for some appropriate meaning of “equal”, as defined by the type’s designer.
Consider creating a structure when one or more of these conditions apply:
The structure’s primary purpose is to encapsulate a few relatively simple data values.
It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an instance of that structure.
Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced.
The structure does not need to inherit properties or behavior from another existing type.
An enumeration defines a common type for a group of related values and enables you to work with those values in a type-safe way within your code.
C enumerations assign related names to a set of integer values. Enumerations in Swift do not have to provide a value for each case of the enumeration.
If a raw value is provided for each enumeration case, the value can be a
string
, acharacter
,integer
orfloat
.
They adopt many features traditionally supported only by classes
enum SomeEnumeration {
// enumeration definition goes here
}
enum CompassPoint {
case north
case south
case east
case west
}
// cases can appear on a single line, separated by commas:
enum CompassPoint {
case north, south, east, west
}
Once
directionToHead
is declared as aCompassPoint
, you can set it to a different CompassPoint value using a shorter dot syntax:
var toward: CompassPoint = .west;
switch toward {
case .west, .south, .east, .north:
print("west direction")
default:
print("Not a direction")
}
Store associated values of other types alongside these case values.
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
var productQRcode = .qrCode("ABCDEFGHIJKLMNOP")
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
print("QR code: \(productCode).")
}
Cases can come prepopulated with default values (
raw values
), which are all of the same type.
// Explicitly assign
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
// Implicitly assign
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
enum CompassPoint: String {
case north, south, east, west
}
let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"
Enumeration initializer that takes a value of the raw value’s type (as a parameter called rawValue) and returns either an enumeration case or nil. You can use this initializer to try to create a new instance of the enumeration.
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
} else {
print("There isn't a planet at position \(positionToFind)")
}
// Prints "There isn't a planet at position 11"
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.