Hawei

Say Hi.


  • Home

  • About

  • Tags

  • Categories

  • Archives

sass.md

Posted on 2020-04-12 | In fcc
  1. 变量

    1
    2
    3
    4
    5
    6
    7
    // 定义
    $main-fonts: Arial, sans-serif;
    $secound-color: ;
    // 使用
    p {
    color: $headings-color;
    }
  2. 嵌套这个很简单就不说了。但是记得&符号

  3. Mixin:可重用代码段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 定义
    @mixin box-shadow($x, $y, $blur, $c){
    -webkit-box-shadow: $x, $y, $blur, $c;
    -moz-box-shadow: $x, $y, $blur, $c;
    -ms-box-shadow: $x, $y, $blur, $c;
    box-shadow: $x, $y, $blur, $c;
    }
    // 使用
    div {
    @include box-shadow(0px, 0px, 4px, #fff)
    }
  4. if-else分支语句
    该分支语句只能用在mixin里

而这个最大的作用就是控制主题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@mixin make-bold($bool) {
@if $bool == true {
font-weight: bold;
}
}

@mixin text-effect($val) {
@if $val == danger {
color: red;
}
@else if $val == alert {
color: yellow;
}
@else if $val == success {
color: green;
}
@else {
color: black;
}
}
  1. for循环

    1
    2
    3
    4
    // scss网格布局
    @for $i from 1 through 12 {
    .col-#{$i} { width: 100%/12 * $i; }
    }
  2. each循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 迭代数组
    @each $color in blue, red, green {
    .#{$color}-text {color: $color;}
    }

    @each $color in blue,black,red {
    .#{$color}-bg {background: $color;}
    }

    // 迭代类对象
    $colors: (color1: blue, color2: red, color3: green);

    @each $key, $color in $colors {
    .#{$color}-text {color: $color;}
    }
  3. while循环

    1
    2
    3
    4
    5
    $x: 1;
    @while $x< 13 {
    .col-#{$x} { width: 100%/12 * $x;}
    $x: $x + 1;
    }
  4. 命名规范
    使用下划线命名: _mixin.scss

    1
    2
    // 引入
    @import 'mixin'
  5. extend 元素重用样式

1
2
3
4
5
6
7
8
9
10
11
.panel{
background-color: red;
height: 70px;
border: 2px solid green;
}

.big-panel{
@extend .panel;
width: 150px;
font-size: 2em;
}

1.function-purity.md

Posted on 2020-04-12 | In funcitonal-js-v3

funciton-purity

function and procedures

  1. 函数: 有输入, 有输出
  2. 程序: 没有输出, 只是在代码段中做了一些事

语义化函数命名

因为函数式编程倾向于声明式编程, 那么对于函数名就要简明易懂.

函数名称的重要性不亚于, 数学中, 公式对应的图像.

副作用

副作用就是, 在函数内部改变了外部变量(包括dom).
常见副作用:

  1. I/O操作(console/file)
  2. 数据库操作
  3. 网络请求调用
  4. DOM操作
  5. Timestamps
  6. 随机数
  7. Cup heat
  8. Cup time Delay

原则: 我们尽量避免副作用,但是,如果无法避免, 那么就让副作用明显一点, 让阅读代码的人看得到.

纯函数和常量

  1. 没有副作用
  2. 没有外部依赖

reducing surface Area

减少可以产生副作用的行为.不要对参数有赋值的念头.

纯函数的确定性: 同样输入,固定输出.

绝对不纯函数

Chapter 3Managing Function Inputs.md

Posted on 2020-04-12 | In light-functional

Managing Function Inputs

all of one

多元转一元

funtion 描述

1
2
3
4
5
function unary (fn) {
return function onlyOneArg(arg) {
return fn(arg)
}
}

arrow function 描述

1
const unary = fn => arg => fn(arg)

explames

1
2
3
4
5
[1, 2, 3].map(parseInt); // [1, na, na]
// 解释一下为什么, parseInt 接收两个参数
// parseInt(a, b)
// a 要转换成整形的值
// b 要转换成数值的形式(二进制, 八进制, 十进制)

one by one

怎么说捏, 这个函数, 用在延迟计算上真的很有用,比如我需要一个值, 这个值在我下一步函数用到,但是这个值, 不能污染我下一个函数啊, 那么我就可以使用组合的方式, 使用这个identity函数去组合.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// function
function identity (v) {
return v;
}
// arrow function
const identity = v => v;


// explames
// 1\. 空值检测
var words = " Now is the time for all... ".split( /\s|\b/ );

words.filter( identity );

// 2\. 参数为函数时, 作为该参数的默认值, 去掉原逻辑中,判断该参数是否函数的默认值
function output(msg,formatFn = identity) {
msg = formatFn( msg );
console.log( msg );
}

function upper(txt) {
return txt.toUpperCase();
}

output( "Hello World", upper ); // HELLO WORLD
output( "Hello World" ); // Hello World

unchanging

某些API不让你直接传入promise, 而是要通过一个返回一个函数来调用的方式, 比如在promise链, then函数中使用另一个promise函数, 这个时候就用到了

1
2
3
4
5
6
7
8
// don't work:
p1.then(foo).then(p2).then(bar)

// instead:
p1.then(foo).then(function () {return p2}).then(bar)

// or use arrow function
p1.then(foo).then(() => p2).then(bar)

FP中有个很好的工具解决这个问题

1
2
3
4
5
6
7
8
9
10
11
12
// function 
function constant (v) {
return function value () {
return v;
}
}

// arrow funtion
const constant = v => () => v;

// so you can use constant function
p1.then(foo).then(constant(p2)).then(bar)

Adapting Arguments to Parameters

先看一个例子

1
2
3
4
5
6
7
function foo(x, y) {
return x + y
}

fucntion bar (fn) {
fn([3, 9])
}

我知道, 我们可以在fn([3, 9])使用解构去处理数组, 但是对于FPer来说, 我们需要更强的可读性, 所以有了下面的小玩具

1
2
3
4
5
6
7
function spreadArgs(fn) {
return function speadFn(argsArr) {
return fn(...argsArr)
}
}

const spreadArgs = fn => argsArr => fn(...argsArr)

但是有时候 , 我们又需要相反的操作.所以出现了下面的另一个小操作

1
2
3
4
5
6
7
fucntion gatherArgs(fn) {
return function gatheredFn(...argsArr) {
return fn(argsArr)
}
}

cosnt gatherArgs = fn => (...argsArr) => fn(argsArr)

Some Now, Some Later —- 偏函数

先看个例子

1
2
3
4
5
6
7
8
9
10
11
function getPerson(data, cb) {
Ajax('http://api/person', data, cb)
}
function getPerson(data, cb) {
Ajax('http://api/person', data, cb)
}

// 这个时候, 我又需要一个定制的接口, 用于获取特定用户的信息
function getCurrentUser (cb) {
getPerson({user: CURRENT_USER_ID}, cb)
}

以上的操作大家都不陌生, 就对于基本函数ajax的一个增强, 比如写死他的url, 写死, 请求方式形成get/post特定的请求方式.但是对于函数式编程来说, 这些都归为偏函数, 让我们来看偏函数这个玩具.

1
2
3
4
5
6
7
function partial (fn, ...presetArgs) {
return function partiallyApplied(...lastArgs) {
return fn(...presetArgs, ...lastArgs)
}
}

const partial = (fn, ...presetArgs) => (...lastArgs) => fn(...presetArgs, ...lastArgs)

好啦, 本质上就是一个闭包的使用,真的超好用. 但是, 对于js本身自带的API里其实就有类似偏函数的功能, 比如bind, 但是呢, bind存在问题,就是硬绑定this, 如果你不需要this, 那么就要传一个null作为一个占位符.你不觉得很很多余吗?

Reversing Arguments

属于部分应用函数的增强.偏函数的传入顺序是左到右, 而reversing的意思就是把传入顺序改为右到左.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function reverseArgs (fn) {
return function argsReversed(...args) {
fn(args.reverse())
}
}

const reverseArgs = (fn) => (...args) => fn(args.reverse())

// 另一种partialRight的技巧
function partialRight (fn, presetArgs) {
return function partiallyApplied (...laterArgs) {
return fn(...laterArgs, ...presetArgs)
}
}

const partialRight = (fn, ...presetArgs) => (...laterArgs) => fn(...later, ...presetArgs)

One at a time

额, 柯里化函数, 很像偏函数, 把需要多个参数的函数变成一系列连续调用的单参数函数.也就类似于, 每个函数都接受一个参数, 并且返回一个函数来接受下一个参数。

来看个场景

1
2
3
4
5
6
curriedAjax('http://some.api/person')({user: CURRENT_USER_ID})(function() { console.log('成功后的回调')})
// 或许以上的代码有点难以理解, 让我们把它拆分成
const personFetcher = curriedAjax('http://some.api/person');
var getCurrentUser = personFetcher({user: CURRENT_USER_ID});
getCurrentUser(function foundUser(user){// 成功后的回调})
// 这两种代码其实都是一个意思, 但是对于FP来说, 第一种风格就是我们提倡使用的

所以让我们来看一下curry玩具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function curry(fn, arity = fn.length) {
return (function nextCurried(prevArgs) {
return function curried(nextArg) {
var args = [...prevArgs, nextArg];

if(args.length >= arity) {
return fn(...args);
}else {
return nextCurried(args);
}
}
})([])
}
const curry = (fn, arity = fn.length, nextCurried) => (nextCurried = prevArgs => nextArg => {
var args = [...prevArgs, nextArg]

if(args.length >= arity) {
return fn(...args)
}else {
return nextCurried(args)
}
})([])

回到一开始的例子, 我们用curry来解决我们遇到的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
const curriedAjax = curry(ajax)
const personFetcher = curriedAjax('http:some/api/person')

const getCurrentUser = personFetcher({user: CURRENT_USER_ID})

getCurrentUser(function callback () {
// do something
})

// 再看一下之前的例子
const add = (a, b) => a + b

[1, 2, 3].map(curry(add)(3)) // 此时add函数中的a为3

柯里化和偏函数的区别, 偏函数是固定某些参数, 而达到增强。而偏函数的目的是适应变化.

Visualizing Curried Functions

来让我们手动的去定义一个curried函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
funtion curriedSum (v1) {
return function(v2) {
return function (v3) {
return function (v4) {
return function (v5) {
return sum (v1, v2, v3, v4, v5)
}
}
}
}
}
// 用箭头函数
const curriedSum = v1 => v2 => v3 => v4 => v5 => sum(v1, v2, v3, v4, v5)

Why Currying and Partial Application?

类比于普通函数, sum(1, 2, 3, 4, 5), 为什么要选择, 柯里化sum(1)(2)(3)这种形式, 或者使用偏函数partial(sum, 1, 2)这种形式呢?

  1. 你可以在某一时刻去指定部分参数, 然后在另一时刻再去指定剩余参数. 而传统的函数则要求所有参数都必须同时存在(传入).
  2. 当只有一个参数时, 函数组合将会更高效, 更容易.一元函数将会更易使用.
  3. 其实柯里化和偏函数增加了代码的可读性,同时使代码更为简洁.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ajax(
'http://some.api/person',
{user: CURRENT_USER_ID},
function foundUser(data) {

}
)
// 让我们使用偏函数去优化它
const getCurrentUser = partial(
ajax,
'http://some.api/person',
{user: CURRENT_USER_ID}
);
getCurrentUser(function foundUser() {
// do something
})

LooseCurry

宽松的curry指的是, 我可以一次传入多个参数,而不是像之前curry每次只传入一个, 来看个实例

1
2
3
4
5
6
// curry
sum(1)(2)(3)


// looseCurry
sum(1, 2)(3)

下面让我们看看looseCurry的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function looseCurry(fn, arity = fn.length) {
return (function nextCurried(pervArgs){
return function curried(...nextArgs) {
var args = [...pervArgs, ...nextArgs]

if(args.length >= arity) {
return fn(...args)
}else {
return nextCurried(args)
}
}
})([])
}

const looseCurry = (fn, arity = fn.length, nextCurried = pervArgs => (...nextArgs) => {
var args = [...pervArgs, ...nextArgs]

if(args.length > arity) {
return fn(...args)
}else {
return nextCurried(args)
}
})([])

No curry

如果你现在有个curry化函数, 你想把它转回正常函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function uncurry(fn) {
return function uncurried (...args) {
var ret = fn

for(let arg in args) {
ret = ret(arg)
}

return ret
}
}

// =>
const uncurry = fn => (...args) => {
var ret = fn

for(let arg in args) {
ret = ret(arg)
}

return ret
}

用命名空间来优化偏函数和柯里化函数, 不受顺序的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function partialProps(fn, presetArgsObj) {
return function partiallyApplied(laterArgsObj) {
return fn(Object.assign({}, presetArgsObj, laterArgsObj))
}
}

function curryProps(fn,arity = 1) {
return (function nextCurried(prevArgsObj){
return function curried(nextArgObj = {}){
var [key] = Object.keys( nextArgObj );
var allArgsObj = Object.assign(
{}, prevArgsObj, { [key]: nextArgObj[key] }
);

if (Object.keys( allArgsObj ).length >= arity) {
return fn( allArgsObj );
}
else {
return nextCurried( allArgsObj );
}
};
})( {} );
}

来看个使用例子

1
2
3
4
5
6
7
8
9
10
11
12
function foo({ x, y, z } = {}) {
console.log( `x:${x} y:${y} z:${z}` );
}

var f1 = curryProps( foo, 3 );
var f2 = partialProps( foo, { y: 2 } );

f1( {y: 2} )( {x: 1} )( {z: 3} );
// x:1 y:2 z:3

f2( { z: 3, x: 1 } );
// x:1 y:2 z:3

总结响应式.md

Posted on 2020-04-12 | In vue

最近在重新梳理知识点,Vue写了那么久, 是时候给自己一个交代了.也不能每天画好玩的UI对吧.

从题目开始, 这篇文章的前置知识点有

  • Object.defineProperty
  • 响应式

Object.defineProperty

关于第一个Object.defineProperty我已经给了链接.

这个方法可以让我们自定义对象上属性的属性.

有点绕啊.举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var obj = {
a: 1,
b: 2
}
Object.defineProperty(obj, 'a', {
// 枚举----你使用for-in/Object.key()的时候会影响到
enumerable: false,
// 可配置---是否可以修改属性的属性, 这就是这个对象里所有的内容
configurable: false,
// 可写---是否可以在这个对象上修改value,比如常规的赋值,删除操作
writable: false,
// 值---你用点运算符操作的时候,读取到的值
value: "static"
})

obj.a // 'static'
Object.keys(obj) // []
for(let i in obj) {console.log(i)} // undefined
obj.a = 1
obj.a // 'static'
Object.defineProperty(obj, 'a', {
// 枚举----你使用for-in/Object.key()的时候会影响到
enumerable: false,
// 可配置---是否可以修改属性的属性, 这就是这个对象里所有的内容
configurable: false,
// 可写---是否可以在这个对象上修改value,比如常规的赋值,删除操作
writable: false,
// 值---你用点运算符操作的时候,读取到的值
value: "1"
}) // trow Error
var unknow = obj.b
Object.defineProperty(obj, 'b', {
// 当然我们也可以重写它的默认的get/set行为
get() {
return unknow * 4
},
set(value) {
unknow = value
}
})
obj.b // 8
obj.b = 2 // 2

这个方法相关的我们就复习完了.

响应式

先说定义, 通俗一点说的响应式是指, 当数据a变化了, 与这个数据a相关的操作都会更新. 来看个非响应式的例子可能会好理解一点.(反向操作)

1
2
3
4
5
let a = 1;
let b = a * 5;

a = 2;
b; // 5 我们期望, 如果a改变,那么基于a的计算都可以同步, 我们希望b为10

那么怎么做到这一点?

我们监测变量a, 如果a触发了get操作, 那么很可能,进行了依赖性的运算, 在这个例子里就是

1
let b = a * 5

在这里b的计算依赖于a.噢, 所以,在这个地方我们获取了a, 并进行了计算, 那我就拿个小本子把这个操作记下来.

如果, 变量a触发了set操作, 也就是赋值操作, 那么我们就要拿出小本子, 在把上面记下来的依赖重新执行一遍就好.

其实这就是响应式的原理.

  1. 收集依赖
  2. 侦测变化
  3. 触发更新

之前我们也提到了, defineProperty我们通过get方法知道什么时候收集依赖, 通过set方法知道什么时候发生了变化, 触发更新.

接下来就是实操了.

把对象所有的属性转换为get/set

defineProperty不像proxy,它只能单个的去监听对象上的属性,而proxy这个小玩具.就很有意思.

那么请听题: 假设你有一个对象(你在骗自己), 你希望有一个函数, 这个函数把这个对象上所有的属性转换为可监测get/set。如果触发了get需要在控制台输出

1
`get: ${key}: ${value}`

一定要自己试一下.以下是答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const transform = (obj, i) => {
var unKnow = obj[i]
Object.defineProperty(obj, i, {
get() {
console.log(`getting"${i}": ${unKnow}`, unKnow)
return unKnow
},
set(value) {
console.log(`setting"${i}" to: ${value}`)
unKnow = value
}
})
}

function convert (obj) {
// Implement this!
for (let i in obj) {
transform(obj, i)
}
}

// test
let a = {
a: 1,
b: 2
}

convert(a)

Vue中的依赖跟踪

data

每个vue的实例都会有一个watcher对象,这个对象中提供增加依赖和触发依赖更新的方法。

在getter操作时把依赖传入Watcher, 而在单个属性发生改变,也就是set的时候触发依赖更新notify, 继而Watcher执行视图更新操作。

注意, 在这里用了设计模式中的发布-订阅模式

每个vue实例就是订阅者, 这些订阅者的更新事件都放到watcher对象里管理。当我们增加一个依赖项的时候把依赖项放入订阅队列, 然后在每次更新的时候区触发对应的事件。完成更新

来看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 一个依赖项就是一个watch
// watch 中的subscibers是订阅者的一个队列,里面存放每个依赖事件
// 方法:
// depend: 为该属性增加依赖项
// notify: 数据更新时通知依赖项更新
// 2. 依赖项
class Dep {
constructor() {
this.taskList = new Set()
}

getDep() {
if (activeUpdate) {
// 为啥这个要使用外部变量不用传参的方式传进来呢?
// 依赖收集的时候,在get方法内部, 在内部我们怎么访问到
this.taskList.add(activeUpdate)
}
}

notify() {
this.taskList.forEach(item => item())
}
}

let activeUpdate = null
// 上面的class部分简单易懂, 不做赘述
// 这个函数一定要明白
function autorun (update) {
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate
update()
// update里面触发数据监听, 会先触发get, 而我们在get里做依赖收集,此时activeUpdate存的是 整个函数体
// 通过这种方式完成了依赖收集, 并且把activeUpdate置空,为下次使用做准备
activeUpdate = null
}
wrappedUpdate()
}

使用get/set方法与依赖收集更新结合, 完成小型的观察者模式

这个可以先自己实现后再往下看.啊?没时间啊?没关系啊, 就…就点收藏就行了啊.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// 2. 依赖项
class Dep {
constructor() {
this.taskList = new Set()
}

getDep() {
if (activeUpdate) {
// 为啥这个要使用外部变量不用传参的方式传进来呢?
// 依赖收集的时候,在get方法内部, 在内部我们怎么访问到
this.taskList.add(activeUpdate)
}
}

notify() {
this.taskList.forEach(item => item())
}
}

let activeUpdate = null

function autorun (update) {
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate // 这个就是一个依赖, 注意,这个其实是是外层函数, 在dep类里, 我们会把它存进taskList, 供通知的时候使用
update() // update里面要执行一个依赖收集
activeUpdate = null
}
wrappedUpdate()
}

// 3. 结合数据变化检测 + 依赖项收集
function scan (obj) {
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
// 在进行响应式的同时初始化依赖项实例, 之后再对应的getter/setter方法中形成闭包, 把依赖状态持久化
const dep = new Dep()
Object.defineProperty(obj, key, {
get () {
// 每次进行get方法的时候, 都进行一次依赖收集
dep.getDep()
return internalValue
},
set (newVal) {
// 检测值是否改变, 如果没有改变, 那么不做处理(为了性能)
const changed = internalValue !== newVal
internalValue = newVal
// 如果发生了改变, 那么在依赖class触发依赖项的更新
if (changed) {
dep.notify()
}
}
})
})
return obj
}



var state = {
count: 0
}

scan(state)

autorun(() => {
// 在state.count就触发了getter操作, 继而触发了依赖收集
a = state.count // 0
})

// 对state.count = 1触发了setter操作, 继而触发了依赖更新
state.count = 1

a // 1

state.count = 2

a // 2

看懂的时候觉得真的, 尤雨溪太他妈帅了。这东西写得好精巧啊。

总结

重新理一下,如果你要做到响应式.那么你应该有什么?

  1. 你要有监测机制, 因为如果你不知道什么时候改变,那么你就不会知道啥时候响应
  2. 你要有依赖收集, 因为你不能预知依赖到底有多少, 那么你就得管理依赖项
  3. 你要有响应机制, 你检测到了更新, 继而触发依赖收集, 下一步就是在数据更新的时候, 根据收集到的依赖, 去触发响应, 更新依赖项

以上这三点, 的实现分别为:

  • 检测机制用 get/set方法进行检测, 作为依赖收集, 触发响应的事件分发点
  • 依赖收集和响应机制我们使用dep这个类来完成, 供检测机制调用
  • 使用autorun包裹存在依赖的操作, 并生成引用, 供dep类的getDep依赖收

完结, 撒花, 满地打滚求点赞.

coursera_go.md

Posted on 2020-04-12 | In golang

char3: go中常见的复杂数据结构

Array

属性

  1. 定长, 超出报错
  2. 类型统一
  3. for range循环
  4. 初始化, 数组每个元素值为所选类型的零值
    1
    2
    var x [5]int  // 长度为5的数组
    var y = [5]int{1, 2, 3, 4, 5} // 有初始值的数组

切片

  1. 容量和长度的概念
    1
    2
    len()
    cap()
  2. 初始化
    1
    2
    3
    arr :=[...]string{"a", "b"}
    s1 := arr[0: 1]
    s2 := arr[1]
    使用make初始化数组
    1
    2
    // 数组类型, 数组长度, 数组容量
    sli = make([]int, 10, 20)
  3. 可扩展
  • append: 数组尾部插入数组
    1
    2
    sli = make([]int, 0, 3)
    sli = append(sli, 100)
  1. 切片也为引用类型

Hash 我觉得有点像js的对象

  1. 展示类型为键值对
  2. key唯一
  3. 查找速度快
  4. 在go中体现为map数据结构

map

map是go中的hash结构

  1. 使用make创建一个map
    1
    2
    3
    4
    5
    6
    7
    // 1
    var idMap map[string]int
    // 2
    idMap = make(map([string]int))
    // 3
    idMap := map[string]int {
    "warning" : 1}
  2. 修改和新增: 和js对象一样
  3. 删除一个key-value——-delete(map, key)
  4. 判定一个key是否在对象中: 使用双赋值map
    1
    2
    // p是一个布尔值, true证明joe在idMap中,反之, 则不在
    id, p := idMap["joe"]
  5. len()获取map的key数量
  6. 遍历使用for-range

Structs

  1. 类似class但是并不是class, 有自己的属性, 自己的方法, 也可以根据这个结构体构造实例
  2. 定义:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type struct Person {
    name string
    addr string
    phone string
    }

    var p1 Person
    // 访问变量
    p1.name = "joe"
    x = p1.addr
  3. 初始化一个结构体
    1
    2
    3
    4
    // 1. use New keyword
    p1 := new(Person)
    // 2. 使用构造函数
    p2 := Person(name: "joe", addr: "s", phone: "yooooh")

charper4: RPC JSON file_io

  1. go提供了rpc协议的一些基础包
  • net/http : web communication protocol
  • net : TCP/IP and socket programming
  1. json: json比struct更为标准化和通用化

    1
    2
    // go struct
    p1 := Person(name: "joe", addr: "a st.")
  2. json的特性

  • 支持所有编码方式
  • 有好的可读性
  • 可以混如不同的类型
  • 使用json.marshalling来对struct与json进行格式转换
  • json.marshal额, 就是反过来
  1. file
  • 访问方式为线性访问, 而不是随机访问
  • 占用线程
  • 分片操作

基本操作步骤:

  • 打开文件
  • 读取byte
  • 写入byte
  • 关闭文件(解锁)
  • seek

文件io涉及的包: ioutil

1
2
3
4
5
6
7
8
// 文件读取操作
// 文件过大时 ,不可使用readfile, 因为会把内存挤爆
data, e := ioutil.ReadFile("test.txt")

// 文件写操作
data = "Hello world"
// 这里最后一个参数是, 新文件的权限, 参照linux文件系统
err := ioutil.WriteFile("output.txt", data, 0777)
  1. io
  • os.Open() 打开一个文件, 返回一个文件的描述
  • os.Close() 关闭一个文件
  • os.Read() 读取一个文件, 以字节流的形式, 可以控制流的大小
  • os.Write() 写一个文件, 以字节流的形式, 可以控制流的大小
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // opening and reading
    f, err := os.Open("test.txt")
    barr := make([]byte, 10)
    nb, err := f.Read(barr)
    f.Close()
    // creating and writing
    f, err := os.Create("output.txt")
    barr := []byte{1, 2, 3, 4}
    nb, err := f.Write(barr)
    nb, err := f.WriteString("Hi")

function: 最主要的是函数名和注释吧, 给足够的信息让后来人尽可能的读懂

  1. 定义: func

    1
    2
    3
    4
    func add(a, b int) int {
    result := a + b
    return result
    }
  2. 在go中, 传入的参数都会被复制一份, 与原数据独立开来。

  • 但是我们需要的话, 我们可以通过指针类型(*, &)传入, 然后对指针地址内的值进行操作, 这样会触发函数内部修改外部函数的效应.
1
2
3
4
5
6
7
8
9
func foo(y *int) {
*y = *y + 1
}

func main() {
X := 2
foo(&x)
fmt.Print(x) // 3
}
  • 入参为数组或者slice,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 数组
    func foo(x [3]int) int {
    return x[0]
    }

    func main() {
    a := [3]int{1, 2, 3}
    fmt.Print(foo(a))
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    func foo(sli []int) {
    sli[0] = sli[0] + 1
    }

    func main() {
    a := []int{1, 2, 3}
    foo(a)
    fmt.Print(a)
    }
  1. 复杂度:

    • 超大函数分解: 要求语义化和可读性
    • 控制流复杂度: 减少嵌套的控制流, 切分函数, 函数分层可以让你的控制流复杂度降低
  2. 写好函数

    • 可读性
  3. 函数组织方式

    • 函数名要有意义
    • 单一职责
    • 更少的参数

函数类型

  1. first-class funciton

    • 函数在go中其实也是一种类型
    • 函数可以赋值给变量, 这样可以以变量名调用函数
    • 可以当作变量在函数中充当入参和出参
  2. go中, 也存在类似js的变量作用域, 叫词法作用域

  3. 闭包

  • function + its environment
  • 当函数完成后, 还可以访问他的environment的能力(数据持久化)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 函数赋值
    var funcVar func(int) int
    func incFn(x int) int{
    return x + 1
    }

    func main() {
    funcVar = incFn
    fmt.Print(funcVar(1))
    }
    1
    2
    3
    4
    // 函数作为参数
    func applyIt (afunc func (int) int, val int) int{
    return afunc(val)
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 匿名函数
    func applyIt (afunc func (int) int, val int) int{
    return afunc(val)
    }
    func main() {
    v := applyIt(func (x int) int {
    return x + 1
    }, 2)
    fmt.Print(v)
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 返回一个函数
func MakeDisOrigin(o_x, o_y, float64) func (float64, float64) float64 {
fn := func (x, y float64) float64 {
return math.Sqrt(math.Pow(x - o_x, 2) + math.Pow(y - o_y, 2))
}

return fn
}

func main() {
Dist1 := MakeDisOrigin(0, 0)
Dist2 := MakeDisOrigin(2, 2)
fmt.Print(Dist1(3, 3))
fmt.Print(Dist2(4, 4))
}

可变函数参数

  1. 使用…收集不确定数量的函数参数, 然后把这个参数当作slice处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    func getMax(vals ...int) int {
    maxV := -1
    for _, v := range vals {
    if v > maxV {
    maxV = v
    }
    }

    return maxV
    }
  2. 使用…展开slice

    1
    2
    3
    4
    5
    func main() {
    fmt.PrintIn(getMax(1, 2, 3, 4, 5))
    vslice := []int{1, 2, 3, 4, 5}
    fmt.PrintIn(getMax(vslice...))
    }

异步函数-defer

  1. 普通模式, 在同步代码调用后, 执行def异步代码
    1
    2
    3
    4
    func testDef() {
    defer fmt.Print("Defer")
    fmt.Print("Hello")
    }
  2. 参数判定
    在go中的def后面执行的代码段相当于一个匿名函数.所以,对于原语类型, 则为其分配一个新的内存控件,对于引用类型, 则通过引用的方式使用.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 原语类型
    func main() {
    i := 1
    defer fmt.Println(i + 1) // 2
    i = i + 3 // 4
    fmt.Println(i)
    fmt.Println("Hello")
    }
    // 4
    // Hello
    // 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 引用类型
func main() {
testDefArgs()
}

func testDefArgs () {
sli1 := []string{"1", "2", "3"}
defer testDef(sli1)
sli1[1] = "123"
fmt.Println("__end__")
}

func testDef(s []string) {
fmt.Println(s)
}
// __end__
// [1, 123, 3]

Class and encapsulation

  1. class的基本概念
    • 模板和实例的关系
    • 私有变量的概念
  2. 链接变量和func
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type MyInt int
    func (mi MyInt) Double () int {
    return int(mi * 2)
    }

    func main() {
    v := MyInt(3)
    // 注意, 这里触发了隐式传值
    fmt.Println(v.Double())
    }
  3. 自定义Struct
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    type Point struct {
    x float64
    y float64
    }

    func (p Point) DistToOrig () {
    t := math.Pow(p.x, 2) + math.Pow(p.y, 2)
    return math.Sqrt(x)
    }

    func main() {
    p1 := Point(3, 4)
    fmt.Println(p1.DisToOrig())
    }

封装

  1. 封装公共方法/也可以说是go中的模块化机制(外部可调用方法, 方法内部存私有变量)
    (类似js中的export/import)

如果方法或者变量使用大写, 那么会被go自动导出.

1
2
3
4
5
6
7
// data.go
package data // 定义data包

var x int = 1
func PrintX() {
fmt.Println(x)
}
1
2
3
4
5
package main
import "data"
func main() {
data.PrintX() // 此时data中封装了PrintX方法, 并且在data中的x是私有变量
}
  1. 把变化封装进struct中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // data.go file
    package data

    type Point struct {
    x float64
    y float64
    }

    func (p *Point) InitMe(xn, yn float64) {
    p.x = xn
    p.y = yn
    }

    func (p *Point) Scale(v float64) {
    p.x = p.x * v
    p.y = p.y * v
    }

    func (p *Point) PrintMe() {
    fmt.Println(p.x, p.y)
    }

    // and we can use it
    // go.go file

    func main() {
    var p data.Point
    p.Init(3, 4)
    p.Scale(2)
    p.PrintMe()
    }

通过这样的方式, 模拟了class的思想,并且把数据隐藏了起来, 只能通过方法访问和修改.

记住, 在你往struct上挂方法的时候, 一定要使用*StructName。因为我们需要的是struct本身, 而不是他的copy。如果不传入指针, 那么对其的修改实际上是修改copy而不是struct.

class抽象相关概念

  1. 多态
    一个函数在不同的上下文中, 起不同的作用。例如,计算面积的函数

    1
    2
    3
    4
    5
    func Area() float64
    // Rect
    area = base * height
    // Triangle
    area = 0.5 * base * height
  2. 继承
    go没有继承, 取而代之是组合.

  3. 重写
    对父类定义的方法重写.

接口类型

定义

  • 接口是一系列方法的集合
  • 包含接口名称,入参, 返回值
  • 接口是实现而不是接口定义

满足一个接口的条件是

  • 包含接口类型所定义的所有接口, 并且入参和出餐符合接口定义
  • 除此之外, 可以有别的方法(接口)

go通过接口类型来完成多态.

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Shape2D interface {
Area() float64
Perimeter() float64
}

type Triangle {}
func (t *Triangle) Area() float64 {

}

func (t *Triangle) Perimeter() float64 {

}

// 这样我们可以说Triangle满足Shape2D的接口类型

接口类型和组合比较

复杂Struct定义了data和方法.

接口只对我们需要的东西暴露出来.

举个例子: 接口类型的定义和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "fmt"

type Speaker interface {
Speak()
}

type Dog struct {
name string
}

func (d Dog) Speak() {
fmt.Println(d.name)
}

func main() {
// 实例化接口和struct
var s1 Speaker
var d1 = Dog{"dog"}
// 接口过滤Dog中不需要的方法
s1 = d1
fmt.Println(s1)
s1.Speak()
}

使用*调用接口(此时还未实例化接口)

就是我以struct的指针赋予接口实例, 然后使用接口实例调用对应接口.此时, 在接口中,我们值为nil(零值)

1
2
3
4
5
6
7
8
9
10
11
12
13
func (d *Dog) Speak() {
if d == nil { // 当我们没有得到实例后的数据时, 此时d为Struct的零值, 也就是nil
fmt.Println("<noise>")
} else {
fmt.Println(d.name)
}
}

var s1 Speaker
var d1 *Dog

s1 = d1
s1.Speak() // <noise>

使用接口

  1. 函数可变入参类型
    举个栗子:我有个函数fn, 这个函数可以传入x类型也可以传入Y类型
  • 定义一个接口类型z
  • 该接口类型满足fn(x)
  • 该接口类型满足fn(y)
  • 实例化并挂载fn1 在fn1中去判定入参类型然后做什么事
  1. 扩展方法
    1
    2
    3
    4
    5
    6
    7
    // 明确一下, 接口类型也是类型, 我们可以通过这个方式往接口扩展原有方法
    func FitInYard(s Shape2D) bool {
    if(s.Area() > 100 && s.Perimeter() > 100) {
    return true
    }
    return false
    }

空接口

  1. 空接口类型不包含任何方法
  2. 空的接口类型满足任何Struct
  3. 使用空接口扩展的方法可以接受任意类型的参数
    举个栗子
    1
    2
    3
    func PrintMe(val interface{}) { // 这样可以让这个函数接受任何类型的参数
    fmt.Println(val)
    }

类型断言

在上面, 我们提出了, 如果我们使用空接口作为类型, 那么,我们可以让类型系统失效.可是我们想控制这个失效的范围, 那么就出现了类型断言.

1
2
3
4
5
6
7
8
9
10
func DrawShape(s Shape2D) bool {
rect, ok := s.(Rectangle)
if ok {
DrawRect(rect)
}
tri, ok := s.(Triangle)
if ok {
DrawRect(tri)
}
}
1
2
3
4
5
6
7
8
9
// 使用switch优化类型断言
func DrawShape(s Shape2D) bool {
switch:= sh := s.(type) {
case Rectangle:
DrawRect(sh)
case Triangle:
DrawRect(sh)
}
}

错误处理

go程序中,如果出现错误时, 会选择返回error接口类型来指示错误.

如果没有错误则返回nil指示一切正常.

1
2
3
type error interface{
Error() string
}

常见错误处理

1
2
3
4
5
6
7
func readFile() {
f, err := os.Open("/harris/test.txt")
if err != nil {
fmt.Println(err)
return
}
}

go_tour.md

Posted on 2020-04-12 | In golang

变量定义

使用var关键字

  1. 带类型
  2. 可以一次定义多个变量, 如果变量类型相同,只需要写最后一个的变量类型
  3. 在函数中可以使用 := 短声明, 函数体外不可使用

基本类型

go语言的基本类型如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool

string

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 的别名

rune // int32 的别名
// 代表一个Unicode码

float32 float64

complex64 complex128

变量初始化时的值为 ‘零值’

  • 数值类型为 0,
  • 布尔类型为 false,
  • 字符串为 ""(空字符串)。

类型转换

使用 type(value)的方式转换

1
2
3
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

当然, 也有更加简洁的方法:

1
2
3
i := 42
f := float64(i)
u := uint(f)

不同于js, 在GO中, 不允许隐式转换。会编译不通过。

虽然没有隐式转换,但是有类型推导

我们定义一个变量,但是不指定其类型时, 变量进行右查询, 根据右查询的结果决定变量类型.

1
2
var i int
j := i // j 也是一个 int

但当右边包含的是变量可能是int, float或者complex128的时候,取决于精度

1
2
3
i := 42           // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128

常量

常量使用const关键字.但是不可以使用简洁语法(:=)

一个没有显示指定类型的常量由上下文环境决定类型.

for循环

1
2
3
4
sum := 0
for i := 0; i < 10; i++ {
sum += i
}

和js不一样, 不需要有括号, 但是{}, 花括号是必须的.

我们可以把前置语句和后置语句置空.做一个while循环

1
2
3
4
sum := 1
for sum < 1000 {
sum += sum
}

条件语句 if

没有括号,但是花括号是必须的.

1
2
3
   if x < 0 {
return sqrt(-x) + "i"
}

在if语句中,可以在条件语句之前执行一段代码:

1
2
3
if v := math.Pow(x, n); v < lim {
return v
}

if-else 在if中的便捷代码里的变量,我们可以在else语句延用,但是,出了条件语句(if, else语句中), 那就脱离了范围,不可以使用了.换句话说,变量作用域在if, else语句体内

swich

和平常的swich没有什么区别,就是条件语句特性就是可以在判定条件前加一个简洁语句.

1
2
3
4
5
6
7
8
9
10
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}

没有条件的swich可以用来做if-else的多分支语句。

1
2
3
4
5
6
7
8
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}

defer语句 延迟执行

defer 语句会延迟函数的执行直到上层函数返回。

延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。

defer执行顺序,所有的defer都会被压入一个栈, 执行的时候会按照先进后执行的顺序.


指针

go中存在指针, 指针存的是变量的内存地址.

定义的方式是* type, type是值的类型, 也就是这个地址内存的是什么类型的值。初始值是nil

1
var p *int

& 符号会生成一个指向其作用对象的指针。也就是生成一个引用值, 新建一个区域, 存入指向对象的地址。

1
2
3
4
i := 42
p = &i
// 此时, 如果直接println(p) -> 读出来是内存地址 0xc000056058 (类似这种)
// 而使用println(*p)那么就表面,我通过p,去读取内存地址为0xc000056058的值, 此时为42.

*符号表示指针指向的底层的值。我觉得指针操作带来的便利是, 性能, 我们并不需要每个值都copy一份.就像js里的值传递和地址传递.通过指针, 我们可以很大程度的优化我们的代码执行效率.

这也就是间接引用和直接引用的一个区别.间接引用通过指针和 * 来直接读取到被引用的值.

go中么有指针运算(指针运算是什么?)

结构体

怎么说,很像js的对象, 但是又有点不像

定义方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type structName struct {
// 结构体
}

// 例子
package main

import "fmt"

type Vertex struct {
X int
Y int
}

func main() {
fmt.Println(Vertex{1, 2})
}

结构体访问方式

使用.运算符访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

type Vertex struct {
X int
Y int
}

func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}

结构体指针

结构体可以通过指针来访问, 这意味着, 我们可以拥有类似原型链的能力了

这里要说的是, 当我们print(p)的时候, 得出的并不是内存的真实地址, 而是 &{1, 2} 的字符串, 但是此时, p和v是有相同的功能的, 由于p是引用,在P上的操作会直接反应到v上.这像不像js的地址传递.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

type Vertex struct {
X int
Y int
}

func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
}

结构体文法

  1. 结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。这里的概念类似于js的结构赋值, 此时有顺序的概念
    1
    2
    3
    4
    5
    type Vertex struct {
    X, Y int
    }

    var v1 = Vertex{1, 2} // x:1 y:2
  2. 使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)
    1
    2
    3
    4
    5
    6
    type Vertex struct {
    X, Y int
    }

    var v2 = Vertex{x: 2, y: 1} // x:1 y:2
    var v3 = Vertex{y: 2, x: 1} // x:1 y:2
  3. 特殊的前缀 & 返回一个指向结构体的指针。使用结构体的指针和使用结构体本身没多大区别, 这一点要牢记.
    1
    2
    3
    4
    5
    type Vertex struct {
    X, Y int
    }

    var v4 = &Vertex{1, 2}

数组

定义方式

1
2
3
var arrayName [length]Type // 意思是 1. 我要定义一个名字为arrayName的数组 2.它的长度为length 3.里面的元素是Type类型的

var a [18]int

注意,在go中, 数组的长度是不可变的.但是go提供了一种机制解决这个问题.

slice

定义一个slice, 这个类似于js中的temp数组.临时数组.

1
2
[]Type // 类型为Type的slice
p := []int{2, 3, 4, 5, 6, 7}

我们可以访问的slice的长度信息通过len(p)

slice切片处理

表达式

1
s[low:high] // 对数组s, 切片从low到high-1

slice数组产生方式二 make()

1
2
3
4
5

a := make([]int, 5) // len(a) -> 5

// 除了指定长度, 我们也可以指定数组容量, 前面说过了, 在go中,数组的长度是固定的, 超出就会报错
b := make([]int, 0, 5) // length(b) -> 0 cap(b) -> 5

slice的初始值 nil

一个slice的初始值为nil, 特征是长度和容量都为0

1
2
3
4
5
var z []int
fmt.Println(z, len(z), cap(z))
if z == nil {
fmt.Println("nil!")
}

往数组中添加元素 append

go内建函数提供往数组尾部插入元素的方法

1
2
3
func append(s []T, vs ...T) []T
// 使用实例
a = append(a, 2, 3, 4)

参数解读
s为类型为T的数组
其余类型为T的数组元素都会添加到slice数组中
输出是, 类型为T的数组S加上参数T的类型归集为一个slice
如果数组s太小, 那么go会为其分配一个更大的数组.返回的slice会指向这个新数组

Range

for循环的range格式可以遍历slice或者进行map循环

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}

如果想用range循环又不需要index, 或者value, 或者两样都不需要, 那么你可以用_符号忽略, 并且仍然使用循环体.

Map键值对

map使用前必须使用make创建

没有使用make的map值为nil的map是空的,并且不能赋值

使用make并没有赋值的时候,会按类型初始化.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义一个键值对
type Vertex struct {
a, b int
}

var m map[string]Vertex

func main() {
m = make(map[string]Vertex)
m["yea"] = Vertex{
40, 20
}
fmt.println(m["yeah"])
}

Map语法:类似于结构体, 但是key值必须有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

type Vertex struct {
Lat, Long float64
}

var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}

func main() {
fmt.Println(m)
}

如果定义value值的类型和顶层的类型一致, 那么可以省略value值的类型名称(Vertex)

1
2
3
4
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}

操作map

  1. 增加key:value
    1
    m[key] = element
  2. 获取元素
    1
    element = m[key]
  3. 删除元素
    1
    delete(m, key)
  4. 检测某个元素在不在
    1
    elem, ok = m[key] // 如果key在m中, 那么ok为true, 否则ok为false, 并且elem为map元素m的零值
    同时, 如果我们从m中获取不存在的key那么我们会得到m的零值

在go中,函数也是值, 这一点和js很像了, 意味着我可以使用函数定义式和函数表达式.

同样,在go中,函数是可以形成闭包的.

闭包是一个函数值,它来自函数体的外部的变量引用。 函数可以对这个引用值进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import "fmt"

func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}

func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
```
那有闭包就可以有FP了.有意思了哦.

我居然写不出go的斐波那契数列.失落.
## 方法
go中没有类, 但是在结构体中可以定义方法
```go
package main

import (
"fmt"
"math"
)

type Vertex struct {
X, Y float64
}

func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}

方法: 给非结构体声名方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"math"
)

type MyFloat float64

func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}

func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}

以上,定义了一个类型, 这个类型继承于float64, 然后呢, 我给这个类型增加了方法Abs, 此时, 该方法和平常结构体的方法调用方式一致.

指针方法与结构体方法的区别

在我理解, 定义一个结构体方法, 该方法内, 我去修改结构体的变量, 此时, 并不会反应到结构体挂载的变量上.但是我用了指针方法, 那么此时, 相当于我可以取到当前实例,的结构体本身, 继而对结构的变量进行修改.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
// "math"
)

type Vertex struct {
X, Y float64
}

func (v Vertex) Abs() float64 {
v.X = v.X * 2
v.Y = v.Y * 2
result := v.X * v.Y
return result
}

func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}

func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
fmt.Println(v) // 3, 4
v.Scale(10)
fmt.Println(v) // 30 40
}

指针函数

入参指针的函数, 入参必须为指针.

而在指针结构体上定义的函数, 调用者可为结构体的指针类型, 也可以为结构体本身.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

type Vertex struct {
X, Y float64
}

func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}

func ScaleFunc(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}

func main() {
v := Vertex{3, 4}
v.Scale(2)
ScaleFunc(&v, 10)

p := &Vertex{4, 3}
p.Scale(3)
ScaleFunc(p, 8)

fmt.Println(v, p)
}

同样的道理, 如果你定义了一个函数, 这个函数的入参规定为值类型, 而非指针类型,那么传入指针会导致程序在编译的时候抛出异常。

而在指针结构体上定义的函数, 调用者可为结构体的指针类型, 也可以为结构体本身.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"math"
)

type Vertex struct {
X, Y float64
}

func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func AbsFunc(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
fmt.Println(AbsFunc(v))

p := &Vertex{4, 3}
fmt.Println(p.Abs())
fmt.Println(AbsFunc(*p))
}

说了那么多指针的优点, 就是想告诉你, 多用指针类结构体定义的函数.

  1. 在指针结构体上定义的函数, 可以修改实例的值(通过指针)。
  2. 避免在每次调用该方法的时候 ,复制使用到的值(优化性能)

原则: 所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"math"
)

type Vertex struct {
X, Y float64
}

func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}

func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
v := &Vertex{3, 4}
fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
v.Scale(5)
fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}

接口类: 是由一组方法签名定义的集合。

怎么解释捏, 就是, 这是一个代理, 这个代理上, 有被代理对象的所有方法.我们可以通过这个代理对象去调用被代理对象的方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"fmt"
"math"
)

type Abser interface {
Abs() float64
}

func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}

a = f // a MyFloat 实现了 Abser
a = &v // a *Vertex 实现了 Abser

// 下面一行,v 是一个 Vertex(而不是 *Vertex)
// 所以没有实现 Abser。
a = v

fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}

type Vertex struct {
X, Y float64
}

func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

接口与隐式实现

通过上面的代码示例我们可以看出来, 在go中,接口直接通过interface类去实现.

假设我定义了一个接口F有方法Z

我定义了一个结构体W, 在这个结构体实现了Z

那么此时, 我声明一个接口类F, 然后, 给它赋W的实例.

此时, F就可以直接调用方法Z.形成了一个代理.

但是, F不可以访问Z上的自定义属性, 只能访问到Z与F共同定义的方法.也就是接口传递, 不传递属性.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

type I interface {
M()
}

type T struct {
S string
}

// 此方法表示类型 T 实现了接口 I,但我们无需显式声明此事。
func (t T) M() {
fmt.Println(t.S)
}

func main() {
var i I = T{"hello"}
i.M()
}

接口类型

接口值包含两个东西1. 被代理对象 2. 被代理的类型.

接口本身也是值, 可以像普通的类型进行传递.

从下面的实例代码我们可以看出, GO的接口类型.跟基本类型一样,可以进行重复赋值, 可以转移.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"fmt"
"math"
)

type I interface {
M()
}

type T struct {
S string
}

func (t *T) M() {
fmt.Println(t.S)
}

type F float64

func (f F) M() {
fmt.Println(f)
}

func main() {
var i I

i = &T{"Hello"}
describe(i)
i.M()

i = F(math.Pi)
describe(i)
i.M()
}

func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}

底层值为 nil 的接口值

udemy_go_2019.md

Posted on 2020-04-12 | In golang

call-apply-bind.md

Posted on 2020-04-12 | In interview

call

先看call做了什么?

  1. call的第一个参数为函数体内部的this指向
  2. 其余作为参数单个传入(非数组)函数体
  3. 立即执行函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Function.prototype.myCall = (context) => {
    if(typeof this !== 'function') {
    throw new TypeError('Error')
    }

    context = context || window
    context.fn = this
    const args = [...arguments].slice(1)
    const result = context.fn(...args)
    delete context.fn
    return result
    }

    apply

    与call的区别在于第一个参数后的传参方式, 是以数组的方式传入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Function.prototype.myApply = (context) => {
    if(typeof this !== 'function') {
    throw new TypeError('Error')
    }

    context = context || window
    context.fn = this
    const args = [...arguments].slice(1)
    const result = context.fn(args)
    delete context.fn
    return result
    }

bind 主要注意一点是, 记得区分出new和普通调用的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.myBind = (context) => {
if(typeof this !== 'function') {
throw new TypeError('Error')
}

// 获取到函数体
const fnBody = this
const args = [...arguments].slice(1)
return function F() {
// 当使用new 方法调用该函数时
if(this instanceof F) {
return new fnBody(...args, ...arguments)
}
// 正常调用
return fnBody.apply(context, args.concat(...arguments))
}
}

什么是闭包?闭包的作用是什么?.md

Posted on 2020-04-12 | In interview

什么是闭包?闭包的作用是什么?

什么是闭包

Closure是函数有能力记住和访问变量的词法作用域, 在函数本身已经执行完成后。(数据持久性)

闭包出现的原因

由于js引擎的垃圾回收机制, 在执行我们的代码的时候,js维护着一个调用栈。在函数执行完成的时候,由垃圾回收机制去处理这个调用栈(调用栈内包含函数的词法作用域), 要销毁调用栈的时候,发现还存在引用。那么垃圾回机制就不处理它。这就导致这个函数的词法作用域保留了下来,也让该函数具有了数据持久性。有利也有弊,基于垃圾回收机制,如果你的闭包内存有大量数据, 那么它是不会被清除的, 这就需要我们自己手动的去处理它。

闭包的作用

  1. 私有化变量, 封装变化
  2. 构建块级作用域: 关于那道题setTimeout, 应该还有一种答案.虽然看起来比较麻烦,但是它可以跑
    1
    2
    3
    4
    5
    6
    7
    8
    console.log('方案3: setTimeout内闭包');
    for (let i = 0; i <= 3; i++) {
    setTimeout(((i) => {
    return () => {
    console.log(i);
    }
    })(i), i * 1000);
    }
  3. 函数式编程里的偏函数用到了闭包
    1
    2
    3
    4
    5
    6
    const addOperator = x => y => x + y;
    const add1Operator = addOperator(1);
    const add2Operator = addOperator(2);

    add1Operator(1); // 2
    add2Operator(1); // 3

数组去重.md

Posted on 2020-04-12 | In interview

#数组去重

大方向啊,先考虑非嵌套数组去重。

然后拓展题, 尝试一下先写测试的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const test = (fn, arg, result) => {
// 绝对是一个新的思路呢
}

// 解:

// em....我们可以使用, ES6的set
const unit1 = arr => {
return new Set(arr);
}

unit1([1, 2, 3, 1]);

//em....reduce.
const unit2 = arr => {
return arr.reduce((result, item) => {
result.includes(item) ? result : result.push(item);
return result;
}, [])
}

unit2([1, 2, 3, 1, NaN, NaN, -0, 0, +0, null, null]);


// em....map, 感觉和reduce一样一样的, 不写了。
// em....for.
const unit3 = arr => {
const result = []
arr.map(item => {
result.includes(item) ? void 0: result.push(item);
});
return result;
}

unit3([1, 2, 3, 1, NaN, NaN, -0, 0, +0, null, null]);

// em...对象的key值不能重复可以利用一下.
const unit4 = arr => {
let result = [];
let obj = {};
arr.map(item => {
obj[item] = null;
})
for(i in obj) {
result.push(i)
}

return result;
}
unit4([1, 2, 3, 1, NaN, NaN, -0, 0, +0, null, null]);

往深的看,嵌套数组去重.往深了走.

  1. 包含数组

  2. 包含对象

那么子问题是, 如何判断一个对象是不是相同.

数组其实就递归使用上面的方法就好呀.

但是再往深的看,其实这里如果自己写判断相等的函数还是涉及到一些坑, 比如类型。js的类型判断有几个坑, 比如-0, NaN, 所以我墙裂推荐大家使用数组的includes方法,它本身的实现已经考虑到js变态的类型机制了。当然你完全可以自己写判断两个元素是否相等的函数, 这绝对没问题.

<1…345…7>

Hawei

Fullstack Engineer

61 posts
16 categories
3 tags
RSS
© 2022 Hawei
Powered by Hexo
|
Theme — NexT.Muse v5.1.4