Hawei

Say Hi.


  • Home

  • About

  • Tags

  • Categories

  • Archives

7.Object.md

Posted on 2020-04-12 | In deep-js-foundations-v2-noteBook

Object

Overview

  1. this
  2. class {}
  3. Prototype
  4. Inheritance vs Behavior Delegation (继承 vs 代理)

this keyword

this 关键字最大的作用是, 提升了代码的复用性和赋予函数的多态性(通过使用this获取不同的context, 从而函数得到不同的返回)

implicitly && Explicit Binding

隐式绑定

1
2
3
4
5
6
7
8
9
var workspace = {
teacher: 'Kyle',
ask (quetion) {
// this显示的指定到了,调用的对象上, 也就是workspace
console.log(this.teacher, quetion);
}
}

workspace.ask('Why?');

代码复用:让同一个函数在不同的调用条件下得到不同的结果。但是他们引用的都是一个函数。节省了内存, 优化性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function ask(que) {
console.log(this.teacher, que);
}

var workspace1 = {
teacher: 'Kyle',
ask,
}
var workspace2 = {
teacher: 'Suzy',
ask,
}

workspace1.ask('Why?');
workspace2.ask('Why?');

显式绑定: call/apply/bind: 第一个参数就是函数的this.

new keyword

new是第四种调用函数的方式。

问题: new关键字做了哪四件事?

  1. 创建一个新的空对象
  2. 链接这个对象到另一个对象
  3. 和this调用这个函数并且设置一个新的对象
  4. 如果这个函数没有返回一个对象,那么把this返回
    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
    function newOperator(ctor){
    if(typeof ctor !== 'function'){
    throw 'newOperator function the first param must be a function';
    }
    // ES6 new.target 是指向构造函数
    newOperator.target = ctor;
    // 1.创建一个全新的对象,
    // 2.并且执行[[Prototype]]链接
    // 4.通过`new`创建的每个对象将最终被`[[Prototype]]`链接到这个函数的`prototype`对象上。
    var newObj = Object.create(ctor.prototype);
    // ES5 arguments转成数组 当然也可以用ES6 [...arguments], Aarry.from(arguments);
    // 除去ctor构造函数的其余参数
    var argsArr = [].slice.call(arguments, 1);
    // 3.生成的新对象会绑定到函数调用的`this`。
    // 获取到ctor函数返回结果
    var ctorReturnResult = ctor.apply(newObj, argsArr);
    // 小结4 中这些类型中合并起来只有Object和Function两种类型 typeof null 也是'object'所以要不等于null,排除null
    var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null;
    var isFunction = typeof ctorReturnResult === 'function';
    if(isObject || isFunction){
    return ctorReturnResult;
    }
    // 5.如果函数没有返回对象类型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),那么`new`表达式中的函数调用会自动返回这个新的对象。
    return newObj;
    }

default binding

非严格模式下, this指向外部this。

严格模式下,this指向undefined.

尽量避免使用默认绑定。

binding precedence : this判定优先级

  1. Is the function called by new?
  2. Is the function called by call or apply?
  3. Is the function called by on a context object?
  4. Default: global object(strict this pointing undefined)

arrow function and lexical this

箭头函数没有自己的this, 此时, this会往外部找, 直到global object.

resolving this in arrow function

函数外部指的是在定义这个函数时的外部, 下面这个例子,定义函数的位置在workshop对象内,所以,this,并不指向workshop.而是workshop的外部, 也就是window.

1
2
3
4
5
6
7
8
9
10
var workshop = {
teacher: 'Kyle',
ask: (que) => {
console.log(this, que);
}
}

workspace.ask('What?');

workspace.ask.call(workshop, 'Still on this');

实际上, new关键字是一个语法糖: 我要调用这个函数, 在一个新的上下文调用。

class keyword

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
class Workshop {
constructor(teacher) {
this.teacher = teacher;
}
ask (que) {
console.log(this.teacher, que);
}
}

// extends方法, prototype
class AntherWorkshop extends Workshop {
speakUp (msg) {
this.ask(msg);
}

ask1 (msg) {
super.ask(msg); // 调用父级方法
}

ask (msg) {
console.log('children method');
}
}



var deepJs = new Workshop('Kyle');
var reactJs = new Workshop('Suzy');

deepJs.ask('what');
reactJs.ask('what');

fixing in Classes

1
2
3
4
5
6
7
8
9
10
11
12
13
class workshop {
constructor (teacher) {
this.teacher = teacher;
this.ask = que => {
console.log(this.teacher, que);
}
}
}


var deepJs = new workshop('Kyle');

setTimeout(deepJs.ask, 100, 'what?');

4.scopeAndFunctionExpress.md

Posted on 2020-04-12 | In deep-js-foundations-v2-noteBook

Function Expressions

来看个例子:

1
2
3
4
5
6
7
8
9
10
// 函数定义式
function teacher () {}
// 函数表达式
var myTeacher = function antherTeacher() {
console.log(antherTeacher) // 可以访问到, 因为函数定义也属于本级定义域
}

console.log(teacher) // 函数本身
console.log(myTeacher) // function antherTeacher() {....}
console.log(autherTeacher) // Reference Error

这里要注意两个东西: 函数定义, 函数定义表达式

  1. 函数定义会提升,而函数定义表达式不会提升。
  2. 两者在内存中的不一致, 对于函数定义式, 在编译阶段的收集变量的时候就获得了这个函数在内存中的地址, 而对于函数定义表达式来说, var myTeacher属于LHS, 函数定义属于RHS, 在第二阶段 ,也就是执行阶段再执行的函数定义, 所以, 上面那段代码中,最后一个console会爆出引用错误,因为在编译过程中,js编译器在内存中就找不到antherTeacher了。
1
2
3
4
5
6
7
8
9
// 匿名函数表达式
var a = function() {

}

// 命名函数表达式
var a = function b () {

}

匿名函数更加常见, 但是大胡子建议我们尽可能的使用命名函数表达式.(命名表达式的作用在于, 如果爆出error我们可以获得完整的调用栈, 定位到错误的地方.

named function expression

why we need to alway uses named function expression?

  1. Reliable function self-reference: 命名函数提供了一个可靠的自身引用地址, 这对于递归函数来说很重要.(也就是我们第一章的示例中, 函数内部可以通过命名函数的函数名访问到函数本身, 想一下递归), 任何时候,你要访问函数本身,你都可以通过函数名访问到, 而匿名函数就无法访问到.函数也就更内聚。
  2. More debuggable stack traces: 触发错误后, 错误的调用栈会包含命名函数的信息,而不会有匿名函数的信息.
  3. More self-documenting code: 除去1, 2点不说, 多使用命名函数,对于你的代码来说,更具可读性. 我看函数名就知道咋回事了.OK, 如果你觉得麻烦,那么命名完全可以和你的变量一样,但是前提是你的变量也必须具有可读性。每个函数应该都是有意义的,如果你的函数没有意义,你可能要考虑一下你是否需要这个函数, 你不够理解你的代码,是否要把这个函数在切分成更小的模块?

Arrow function

箭头函数本质上是匿名函数,匿名函数就会有上面说的问题。 所以大胡子并不推荐我们无脑使用箭头函数.

function types hierarchy

(named)function declaration >
named function expression >
anonymous function expression

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
}
}

udemy_go_2019.md

Posted on 2020-04-12 | In golang

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 的接口值

vue-cli.md

Posted on 2020-04-12 | In 构建工具

控制环境变量

  1. 新建.env文件
  2. 在代码中使用process.env.NODE_ENV[变量名]访问
  3. .env权重比: .env.[mode].local > .env.[mode] > .env.local > .env

proxy.md

Posted on 2020-04-12 | In 算法数据结构

proxy—(基础)

定义: proxy对js中的所有类型, 做一个代理, 所有操作都会先进入代理, 如果我们有设置对应操作的代理,那么优先使用我们自定义的操作(最常见是get/set).如果没有对应代理, 那么进入正常操作.

来看一下语法:

1
2
3
// target: 被代理的目标
// handler: 代理的行为函数
let p = new Proxy(target, handler);

来看一下mdn上面的示例, 我们能更好的理解proxy的行为

示例1: 代理object的get方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 // 初始化一个handler, 该handler的内容是设置get方法, 如果我们读取了不在该对象上的属性, 则返回默认值37
let handler = {
get: function(target, name){
return name in target ? target[name] : 37;
}
};
// 初始化一个proxy对象 ,
// 参数1是一个空对象, 意味着我们没有对其他变量进行绑定
// 参数2是一个handler重写了get行为
let p = new Proxy({}, handler);

p.a = 1;
p.b = undefined;

console.log(p.a, p.b); // 1, undefined
// 此时,c不在p内, 所以返回默认值37
// 至此, 我们完成了对对象p, get方法的代理.
console.log('c' in p, p.c); // false, 37

代理转发, 数据绑定

简而言之, proxy构造函数的第一个参数,就是被绑定的元素。当我们修改proxy生成的实例时,修改会传递到被绑定的元素上。达到数据绑定的目的.

来看例子:

1
2
3
4
5
6
let target = {};
let p = new Proxy(target, {});

p.a = 37; // 操作转发到目标

console.log(target.a); // 37. 操作已经被正确地转发

验证对象赋值

我觉得代理有点像钩子的概念, 当我加入代理后, 那么我可以重写原有的默认行为(get/set), 也可以在原有的get/set增加逻辑, 就时钩子的概念啊.

这个例子重写了对象的set方法, 在set方法里验证对于属性’age’的赋值是否合法.并且保留默认的set行为.达到了验证的效果.

我们在vue/react都存在对应的props数据验证.在这个地方是否就是用proxy重写的?

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
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}

// The default behavior to store the value
obj[prop] = value;
}
};

let person = new Proxy({}, validator);

person.age = 100;

console.log(person.age);
// 100

person.age = 'young';
// 抛出异常: Uncaught TypeError: The age is not an integer

person.age = 300;
// 抛出异常: Uncaught RangeError: The age seems invalid

高阶应用之: 扩展构造函数

Reflect

这里要引入一个es2015的api.Reflect

Reflect是新增的一个内置对象, 这个对象不是构造函数, 无法使用new操作符,Reflect上所有的方法都是静态的.(类似Math.Refect对象)。

1
2
3
// 例如, get方法
var obj = {a: 1}
Reflect.get(obj, 'a') // ‘a’

所以,其实ReflectAPI实际上是, 把Reflect作为一个静态标识, 类似Math, 然后以第一个参数为this, 去调用this上的get方法.

注意了, 如果Reflect调用时, 对象的默认行为已经被修改, 那么此时, 会触发修改后的行为.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 类似这种效果
var likeReflect = {
get (obj, key) {
return obj['a']
}
}

var obj = {
a: 1
}
var prObj = new Proxy(obj, {
get: function(obj, key){
return obj[key] * 3;
}
})
likeReflect.get(prObj, 'a')

到这里, 关于Reflect的基本知识就足够了.

构造函数扩展

平常,我们都是通过以下方式来完成一个构造函数的扩展, 有了proxy我们就可以用一个新姿势来处理这种场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 手动实现构造函数OLO
function Person(name) {
this.name = name
}

function Man(name, sex) {
Person.call(this, name)
this.sex = sex
}
Man.prototype = Person.prototype
Man.prototype.constructor = Man

a = new Person('lili')
b = new Man('xiaoming', 11)

// 使用工具函数实现OLO
function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}

扩展构造函数

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
function extend(sup,base) {
// 获取属性的属性(枚举/可读/可写/可配置)
var descriptor = Object.getOwnPropertyDescriptor(
base.prototype,"constructor"
);
// 链接原型链, 子类->父类
base.prototype = Object.create(sup.prototype);
// proxy的处理函数,
// 重写construct: 使用上面的sup的新链, 并且调用原有逻辑(新建一个对象, 执行函数体, 返回obj)
// 重写apply: 保证初始化入参都被调用了 this.key = value
var handler = {
construct: function(target, args) {
var obj = Object.create(base.prototype);
this.apply(target,obj,args);
return obj;
},
apply: function(target, that, args) {
sup.apply(that,args);
base.apply(that,args);
}
};
// 以base为基础新建proxy
var proxy = new Proxy(base,handler);
// 重新设置constructor
descriptor.value = proxy;
Object.defineProperty(base.prototype, "constructor", descriptor);
return proxy;
}

var Person = function(name){
this.name = name
};

// 这样就很清晰了
var Boy = extend(Person, function(name, age) {
this.age = age;
});

Boy.prototype.sex = "M";

var Peter = new Boy("Peter", 13);
console.log(Peter.sex); // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age); // 13

使用proxy实现双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};

const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key === 'text') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
}
});

input.addEventListener('keyup', function(e) {
newObj.text = e.target.value;
});

面试手册.md

Posted on 2020-04-12 | In 算法数据结构

// 基于发布订阅模式写一个事件事件发布订阅

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
class Event {
constructor() {
this.taskList = {}
}

on(eventType, fn) {
// 触发时, 明白同一个事件可能有多个任务的可能
// 所以对于task的处理我们都用数组的格式来处理
let fns = ( this.taskList[eventType] = this.taskList[eventType] || [] )
if(!fns.include(fn) {
fns.push(fn)
}
}

emiter(eventType, data) {
let fns = this.taskList[eventType]
if(Array.isArray(fns)) {
fns.map(item => {
item(data)
})
}
return this
}

off(eventType, fn) {
let fns = this.taskList[type]
if(Array.isArray(fns)) {
if(fn) {
let index = fns.indexOf(callback)
if(index !== -1) {
fns.split(index, 1)
}
}else {
fns.length = 0
}
}
return this
}
}

实现一手深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const isType = (value) => {
return Object.prototype.toString.call(value).match(/(?<= )\w+/)[0]
// String Number Null Undefined Object Date RegExp Symbol Boolean Function Array
}
const deepClone = obj => {
let objType = isType(obj)
let result = {}
for(let i in obj) {
if(objType === 'Object' || objType === 'Array') {
result[i] = deepClone(obj[i])
}
result[i] = obj[i]
}
return result
}
a = {
a: 1,
b: {
a: 1
}
}
b = deepClone(a)

千分位

1
2
3
4
5
6
7
8
9
10
11
12
13
function exchange(num) {
num = String(num)
if(num <= 3) {
return num
}

num = num.replace(/\d{1,3}(?=(\d{3})+$)/g, (v) => {
return v + ','
})
return num
}

exchange(1230597)

模拟new关键字

1
2
3
4
5
6
7
8
9
10
function likeNew (fn, ...args) {
let o = Object.create(fn.prototype)
const result = fn.apply(o, args)
return result || o
}

function Person(name) {
this.name = name
}
likeNew(Person, 'weihaidong')

// 闭包setTimeout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1 setTimeout 外闭包
for(var i = 0; i < 4; i++) {
((i) => {
setTimeout(() => {
console.log(i)
}, 1000 * i)
})(i)
}
// 2 setTimeout 内闭包
for(var i = 0; i < 4; i++) {
setTimeout(((i) => {
return () => {
console.log(i)
}
})(i), 1000 * i)
}

isType函数

1
2
3
4
5
const isType = v => {
const reg = /(\.+\s+)/
return Object.prototype.toString.call(v).replace(/(\[\w+\s+)(\w+)(\])/, '$2')
}
isType(1)

古典继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Person(name) {
this.name = name
}
Person.prototype.getName = function() {
return this.name
}

function Women(name, sex) {
// 链接Person属性
Person.call(this, name)
// 初始化自身属性
this.sex = sex
}

// 链接原型链
Women.prototype = Person.prototype
// 修复constructor指向
Women.prototype.constructor = Women

快排

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const quickSort = arr => {
if(arr.length <= 1) return arr
let [min, flag, max] = arr.reduce((sum, cur, index) => {
if(index === 0) {
sum[1].push(cur)
} else {
if(cur >= sum[1]) sum[2].push(cur)
if(cur < sum[1]) sum[0].push(cur)
}
return sum
}, [[], [], []])
console.log(min, flag, max)
min = quickSort(min)
max = quickSort(max)
return [...min, ...flag, ...max]
}

quickSort([2, 1, 3, 5, 6, 1, 6, 7, 8])

二分法查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const binarySearch = (arr, x) => {
console.log(arr)
if(arr.length <= 1) {
return arr[0] === x ? x : false
}
const flag = Math.floor(arr.length / 2)
let result
if(x === arr[flag]) return result = x
if(x > flag) result = binarySearch(arr.slice(flag), x)
if(x < flag) result = binarySearch(arr.slice(0, flag), x)
return result
}
binarySearch([1, 2, 3, 4, 5, 6], 6)
binarySearch([1, 2, 3, 4, 5, 6], 7)

发布订阅

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
class Event {
constructor() {
this.cache = {}
}

addEvent(type, fn) {
if(this.cache[type]) {
this.cache[type].push(fn)
} else {
this.cache[type] = [fn]
}
}

emmitEvent(type, params) {
const taskList = this.cache[type]
if(taskList) {
taskList.map(item => {
item(params)
})
}
}

deleteEvent(type) {
this.cache[type] = []
}
}

const event = new Event()
event.addEvent('fly', (value) => {
console.log('i will fly', value)
})
setTimeout(() => {
event.emmitEvent('fly', 'go')
}, 2000)

6.typescript.md

Posted on 2020-04-12 | In deep-js-foundations-v2-noteBook

typescript

Typescript/flow, and typt aware linting.

typescript benefits:

  1. Catch type-related mistake
  2. Communicate type intent (类型推断)
  3. Provide IDE feedback

caveats

  1. Inferencing is best-guess, not a guarantee
  2. Annotations is optional
  3. Any part of the application that isn’t typed introduces uncertainty.

Inferencing:

在使用静态语言编写代码的时就会进行类型推断,并且与IDE很好的支持(有提示插件)。但是推断解决的问题是, 我们对一个变量不确定类型,很容易造成’xxx is undefined.’等error

在定义时就确定了类型, 在之后给该变量赋值使用不同的类型时,就会报错。

1
2
3
4
5
6
7
8
// example 1
var name = 'keywen'

name = {
value: 'keywen' // throw Error.
}
// example 2: 显示的确定其类型
var name: String = 'keywen'

custom type

自定义类型: 语法

1
2
3
4
5
6
7
8
// ":" 后是指定的类型
type student = {
name: string
}

function getName (studentObj: student): string {
return studentObj.name
}

Validating Operand Types

1
2
3
// case1,在这种情况下,我们不会触发强制, 只会在特定的类型进行运算, 防止出现我们意料之外的错误
var studentName: string = 'Nacy';
var studentSum: number = 16 - studentName; // throw error: can't substract sting

compare typescript with flow

不用比较了typescript已经赢了

两者主要是解决了我们在编程过程中对类型的不确定,让我们的类型更加清晰(前面说过了, 如果你对你代码中的类型不确定,那么, 最好的方式是重构你的代码, 因为你对你的代码还不够了解)

typescript的优点

  1. They make our code more obvious.
  2. more like other language’s systems.
  3. Extremely popular these days.(生态将会更好)
  4. 微软做背书,值得投资学习。
  5. They’re very sophisticated and good at what they do.(根据社区的反馈,他们做得非常好,至少在类型判断这一点上)

typescript的缺点

  1. They use “non-js-standard”syntax(or code comments)
    从长远看, 这是很危险的, 因为不使用标准语法, 那么在未来的某一天,js改了标准,而ts又改了足够多的东西,要么ts继续往js的标准走,要么ts成为另一种方言, 或者取代js
  2. They require a build process, which raises the barrier to entry
  3. Their sophistication can be intimidating to those without prior formal types experience(对于没有静态语言经历的同学就有点可怕了)
  4. They focus more on “static type”(variables, parameters, returns, properties)then value types.(ts更多的关注在变量的静态, 而不是值的静态)

我们知道,代码中的bug很大一部分是type问题造成的,那么ts和flow就解决了这个问题,但要意识到,这并不是唯一的解决方法

总结

  1. js本身拥有一个(动态)类型系统,它使用不同形式的强制转换,value的类型转换,运算符的转换, 等号两边的转换
  2. 然鹅,这样的类型系统大家给出的回应, 或者说解决方案是: 尽可能避免这样的系统,尽可能的使用”===”, 保护你担忧的类型
  3. 避免整个JS的部分问题,比如假装==避免了您需要了解类型,是它倾向于系统地使bug永久存在。在某些场景下,你最好还是使用“==”, 例如你非常了解你的类型系统时, 使用== 会让事情更简单,如果强行使用 === 那么这会造成系统级别的bug.
  4. 如果你不是足够的了解js的类型系统,那么就无法编写高质量的js代码.
  5. 使用静态语言系统或许是你的一个选择, 但不是唯一的解决方案。
  6. js的类型系统相对于静态语言来说的类型系统比较怪异, 但是学习它也是解决类型问题的一种方式
  7. 对于有过静态语言学习经验的开发者来说,js的类型系统对他们来说很难接受,而静态语言学习起来较为容易
  8. 我的主张:更好的方法是拥抱和学习JS的类型系统,并采用一种编码风格,使类型尽可能明显

8.prototype.md

Posted on 2020-04-12 | In deep-js-foundations-v2-noteBook

Prototypes

Objects are build by ‘constructor calls’(via: new)

A “constructor call” makes an object it is own prototype.

(cs101:
什么是类,对一个事物的抽像描述: 属性和方法.这个东西有啥属性, 能做什么。
类和实例的关系: 实例是类的实现。
类的一些特性: 继承和多态。
)

A “constructor call” makes an object linked to its own prototype.

注意区分link(保存引用)和完全复制的区别.js为了性能, 并没有进行完全复制。

所以, js中的class的继承并不是copy而是link, 链接万物。当然,你需要完全copy也不是不可以.

Prototypal class

1
2
3
4
5
6
7
function Workshop (teacher) {
this.teacher = teacher;
}

Workshop.prototype.ask = (que) => {
console.log(this.teacher, que);
}

Dunder Prototypes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Workshop (teacher) {
this.teacher = teacher;
}

Workshop.prototype.ask = (que) => {
console.log(this.teacher, que);
}

var deepJS = new Workshop('Kyle');

deepJS.constructor === Workshop

deepJS.__proto__ === Workshop.prototype;
Object.getPrototypeOf(deepJS) === Workshop.prototype;

Object.setPrototypeOf

正常的函数有prototype, 箭头函数没有.

shadowing prototype

原型链重写会导致很多问题, 导致指向异常。

prototypal inhertance

原型链继承并不是复制, 而是引用。

js中的继承其实更像行为委托(代理).

class并不是唯一解,但是对于一些框架来说,class也许是他们的选择。原型模式对我们来说或许更加的适用。

OLOO Pattern (Object linked to other objects)

recall class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class workshop {
constructor(teacher) {
this.teacher = teacher;
}

ask(que) {
console.log(this.teacher, que);
}
}

class antherWorkshop extends workshop {
speakUp(msg) {
this.ask(msg);
}
}

OLOO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const workshop = {
setTeacher(teacher) {
this.teacher = teacher;
},
ask(que) {
console.log(this.teacher, que);
}
}

const antherWorkshop = Object.assign(Object.create(workshop), {
speakUp (msg) {
this.ask(msg);
}
})

const jsrecentParts = Object.create(antherWorkshop);
jsrecentParts.setTeacher('Kyle');
jsrecentParts.speakUp('What?');

ES5 polyfill: Object.create

1
2
3
4
5
6
7
if(!Object.create) {
Object.create = function (o) {
function F() {};
F.prototype = o;
return new F();
}
}

delegation: design pattern

其实class只是实现相同结构的一条路。我们通过组合, 可以达到和class相同的效果,并且由于js的只引用不复制的一个语言特性下, 我们使用组合或许更加符合js的语言特性.

class的一个思想趋势是, 区分父子关系然后链接。而组合则是点对点的关系。那么组合的层级还有方式就很多。可以说组合让代码的粒度更细了。

还有个好处是,this的指向在组合的设计模式下会更加清晰。因为调用关系明确。判断this指向的四个规则第一条就是, 谁调用, 指向谁。

<1…345…7>

Hawei

Fullstack Engineer

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