PAJK-FE BLOG

我是副标题


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 搜索

房

发表于 2018-06-27 | | 阅读次数

前期准备

  • 五年社保或者税单 (连续63个月满60个月即可)
    社保查询
    税单查询
  • 三年连续居住证或者积分之类的(用于后期的房产税)
  • 6个月的连续的公积金缴费(用于公积金贷款)
  • 结婚证
  • 征信报告
  • 户口本

看房

  • 先选区域 (方便上下班,方便开车,周围配套等)
  • 选好区域,缩小范围,直接去看这个区域内的每一个小区,选好小区
  • 选好小区后,排除法选房 (不要一楼, 不要顶楼,不要主街道旁边,不要车库旁边等)
  • 问好小区周围学校上学的相关信息,观察小区物业等等细节
  • 房子特别需要注意的事情是:
    1. 房子是否有遗产税,是否有抵押,是否是经济适用房。(经济适用房大概有百分之20的增值税)
    2. 动迁房 满五唯一 只需要付一个点的契税 如果满五不唯一,需要付一个点的契税,一个点的个税。
    3. 商品房外环超过230万要付增值税,中环310万内环450万要付增值税,如果是动迁房,二手卖进的话,一样要付增值税。 增值税的计算公式是 差额/1.05×0.05

下定金注意事项

  • 必须要看到产调才可以下定金! 产调上重点看,房屋性质,房屋有无贷款等。
  • 房产证上面的所有的人员到齐才可签合同。
  • 必须列清楚租客搬出时间。
  • 需要注意贷款科是否收费。
  • 最重要的就是 中介费! 可以分成两次交, 一半是过户的时候, 一半是交房的时候!

买房流程

  • 下定金,签合同(合同中有很多附件,里面写清楚了所有的事项,包括房钱多少,什么方式付费,贷款多少, 租客,物业很信息)
    fang2
  • 网签 (去房产交易中心网签,需要产证上所有人到齐, 如果是周末办的话,大早上就过去,限号)
    fang5
  • 付首付, 房款的35%
  • 去贷款科办贷款 (大概半个月到一个半月的时间,贷款审批下来)
    1. 带上的资料有: 身份证,户籍证明, 婚姻证明, 未成年小孩出生证, 定金收据, 首付款收据以及首付款转账凭证, 公积金账号, 上海市房地产买卖合同, 收入证明, 非上海户籍5年以上个人所得税税单或者社保证明, 近一年的工资流水明细
    2. 需要注意的是: 公积金贷款利率是3.25% ,商贷4.95% (现在部分银行能做9折,大多数是95折)
      fang6
  • 贷款审批下来后,要去房产中心过户。(审税,查限购,过户)
  • 三到五个工作日后要去房产中心交税费(契税,个税)
  • 三个工作日后出新的产证和抵押证,将抵押证交给银行,差不多20天左右放款
  • 放款后,一周左右就差不多可以联系房东进行交房了,交房要有水电煤,有线电视更名过户,还有尾款结清

前端基础知识回顾

发表于 2018-05-18 | 阿策 | 分类于 基础知识 | | 阅读次数

前端基础知识回顾

一段html代码

1
2
3
4
5
6
7
8
9
10
11
12
<body>
<a href='https://www.jk.cn/'>点击取消跳转</a>
<ul>
<li>红灯</li>
<li>绿灯</li>
<li>黄灯</li>
</ul>
<script>
var nodeList = document.querySelectorAll('ul li')
var colorList = ['red', 'green', 'orange']
</script>
</body>

一、数组

  1. slice() 和 splice()

    colorList.slice(1) –> [‘green’, ‘orange’]
    colorList.slice(-2) –> [‘green’, ‘orange’]
    colorList.slice(1, 2) –> [‘green’]
    colorList.splice(2, 1) –> [“orange”]; colorList –> [‘red’, ‘green’]

    nodeList.slice(1) –> Uncaught TypeError: nodeList.slice is not a function
    Array.prototype.slice.call(nodeList, 1) –> [li, li]

  2. isArray 及 Polyfill

    Array.isArray(colorList) –> true
    Array.isArray(nodeList) –> false

    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
    	if (!Array.isArray) {  
    Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
    };
    }
    ```

    3. ArrayLike => Array 几种方式

    > var arr = Array.from(nodeList); Array.isArray(arr) --> true
    > var arr = [...nodeList]; Array.isArray(arr) --> true
    > var arr = Array.prototype.slice.call(nodeList); Array.isArray(arr) --> true

    #### 二、事件监听
    1. DOM0、DOM2、DOM3
    > DOM0 注意 onclick 大小写 document.querySelector('ul').onclick = function () { alert('hello world') }
    > DOM2是通过addEventListener绑定的事件, 还有IE下的DOM2事件通过attachEvent绑定

    2. addEventListener第三个参数如果是 useCapture: true 和 false 分别代表什么?事件传播方式(事件冒泡、事件捕获),分别讲述两者不同

    > target.addEventListener(type, listener, useCapture);

    3. 两种事件传播方式的执行顺序,console.log() 执行顺序 (默默的思念旭明1秒钟)

    > 1:事件捕获, 2:处于目标阶段, 3:事件冒泡阶段

    ``` javascript
    var ulEventTarget = document.querySelector('ul')

    ulEventTarget.addEventListener('click', function (e) {
    console.log('ul', '捕获')
    }, true)

    ulEventTarget.addEventListener('click', function (e) {
    console.log('ul', '冒泡')
    }, false)

    nodeList[0].addEventListener('click', function (e) {
    console.log('li', '冒泡')
    }, false)

    nodeList[0].addEventListener('click', function (e) {
    console.log('li', '捕获')
    }, true)
  3. 点击穿透 stopPropagation() 取消冒泡或者捕获 及 取消跳转:preventDefault() 取消默认事件;

三、作用域 – 最小访问

1
2
3
4
5
for (var i = 0; i < nodeList.length; i++) {
nodeList[i].addEventListener('click', function (e) {
e.target.style.color = colorList[i]
})
}
  1. i –> 3
  2. colorList[i] –> undefined
  3. 如何解决

    3.1 简单方便的绑定一次,复制两次,改一改,优点:缓解脱发

    1
    2
    3
    4
    5
    6
    7
    8
    9
    nodeList[0].addEventListener('click', function (e) {
    e.target.style.color = colorList[0]
    })
    nodeList[1].addEventListener('click', function (e) {
    e.target.style.color = colorList[1]
    })
    nodeList[2].addEventListener('click', function (e) {
    e.target.style.color = colorList[2]
    })
> 3.2 发现代码冗余后,抽取一个公共方法出来,一个莫名其妙的闭包就诞生了

1
2
3
4
5
6
7
8
var handleEvent = function (color) {
return function (e) {
e.target.style.color = color
}
}
nodeList[0].addEventListener('click', handleEvent(colorList[0]))
nodeList[1].addEventListener('click', handleEvent(colorList[1]))
nodeList[2].addEventListener('click', handleEvent(colorList[2]))
> 3.3 发现可以帅帅的写个循环,发现代码行数竟然没啥变化,好沮丧啊! > 闭包 密闭的空间,块级作用域 > 执行方法的时候,可以创建一个新方法,同时开辟一块空间 > 此时新函数,保留了创建函数时的所需要参数 color 的值 > (录制了案发现场,储存 color 路过时的痕迹) > > console.log(this) --> `<li>红灯</li>` > console.log(this) --> `<li>绿灯</li>` > console.log(this) --> `<li>黄灯</li>`
1
2
3
4
5
6
7
8
9
10
var handleEvent = function (color) {
return function (e) {
e.target.style.color = color
console.log(this)
}
}

for (var i = 0; i < nodeList.length; i++) {
nodeList[i].addEventListener('click', handleEvent(colorList[i]))
}

四、ES6

1
2
3
4
5
6
7
const len = nodeList.length
for (let i = 0; i < len; i++) {
nodeList[i].addEventListener('click', e => {
console.log(this)
e.target.style.color = colorList[i]
})
}
  1. 箭头函数

    console.log(this) –> Window (指向外层 this)

  2. 阐述 let & const & var 区别、作用.

    let、const 块级作用域, const/let 不/允许重新赋值.
    colorList[i] –> ‘red’
    colorList[i] –> ‘green’
    colorList[i] –> ‘orange’

  3. 细节 避免重复查询数组长度(length)

    const len = nodeList.length

  4. for of 及 array.entries() 的应用

1
2
3
4
5
6
const handleEvent = color => e => e.target.style.color = color
const iterator = nodeList.entries();

for (let [i, node] of iterator) {
node.addEventListener('click', handleEvent(colorList[i]))
}

五、类、oop

  1. 类的定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function Light(dom, color = "grey") {
    this.color = color
    this.isLight = false
    this.dom = dom
    this.turnOn = function() {
    this.isLight = true
    this.dom.style.color = this.color
    }

    this.turnOff = () => {
    this.isLight = false
    this.dom.style.color = 'grey'
    }

    this.switch = () => {
    this.isLight ? this.turnOff() : this.turnOn()
    }
    }
  2. 事件代理 及 switch case 使用 && LampBox.red.switch === LampBox.green.switch ?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    var LampBox = {}

    colorList.forEach((color, index) => {
    LampBox[color] = new Light(document.querySelectorAll('li')[index], color)
    })

    // console.log(LampBox.red.switch === LampBox.green.switch) false

    document.querySelector('ul').addEventListener('click', e => {
    switch (e.target.innerText) {
    case '红灯':
    LampBox.red.switch()
    break;
    case '绿灯':
    LampBox.green.switch()
    break;
    case '黄灯':
    LampBox.orange.switch()
    break;
    default:
    console.warn('bulubulu')
    }
    })
> 借助 prototype 修改 Light

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Light(dom, color = "grey") {
this.color = color
this.isLight = false
this.dom = dom
}

Light.prototype.turnOn = function() {
this.isLight = true
this.dom.style.color = this.color
}

Light.prototype.turnOff = function() {
this.isLight = false
this.dom.style.color = 'grey'
}

Light.prototype.switch = function() {
this.isLight ? this.turnOff() : this.turnOn()
}
  1. Map 操作 get、set

    1
    2
    3
    4
    5
    6
    var map = new Map()

    for (var i = 0; i < nodeList.length; i++) {
    map.set(nodeList[i], new Light(nodeList[i], colorList[i]))
    }
    document.querySelector('ul').addEventListener('click', e => map.get(e.target).switch())
  2. css 动画、异步操作

    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 map = new Map()

    function doScaledTimeout(i, time, cb) {
    setTimeout(function() {
    cb && cb(i)
    }, time);
    }

    const createLight = (e, color) => {
    if (color !== 'orange') {
    return new Light(e, color)
    }
    var yellowL = new Light(e, color)

    yellowL.turnOn = function() {
    this.isLight = true

    // css 动画
    this.dom.style.transition = 'color'
    this.dom.style.transitionDuration = '.5s'

    // color: 1000 yellow 1500 grey 2000 yellow 2500 grey 3000 yellow 3500 grey s
    var i = 0
    while (i < 3) {
    // 异步操作
    setTimeout(() => {
    this.dom.style.color = this.color
    }, 1000)
    setTimeout(() => {
    this.dom.style.color = 'grey'
    }, 500)
    i++
    }
    }
    return yellowL
    }

    for (var i = 0; i < nodeList.length; i++) {
    map.set(nodeList[i], createLight(nodeList[i], colorList[i]))
    }

    document.querySelector('ul').addEventListener('click', e => map.get(e.target).switch())
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
while (i < 3) {
// 需要闭包
setTimeout(() => {
this.dom.style.color = this.color
}, i * 1000);
setTimeout(() => {
this.dom.style.color = 'grey'
// i => 3
if (i === 2) {
this.isLight = false
}
}, i * 1000 + 500);
i++
}

while (i < 3) {
doScaledTimeout(i, i * 1000, () => {
this.dom.style.color = this.color
})
doScaledTimeout(i, i * 1000 + 500, (i) => {
this.dom.style.color = 'grey'
if (i === 2) {
this.isLight = false
}
})
i++
}
  1. Promise 解决方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function turnOn() {
    return new Promise(function(resolve, reject) {
    setTimeout(() => {
    this.dom.style.color = this.color
    resolve(500)
    }, 1000);
    }.bind(this))
    }

    function turnOff() {
    return new Promise(function(resolve, reject) {
    setTimeout(() => {
    this.dom.style.color = 'grey'
    resolve()
    }, 500);
    }.bind(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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    function doPromiseTimeout(i) {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    this.dom.style.color = this.color
    resolve(500)
    }, 1000);
    }).then(delayMS => {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    this.dom.style.color = 'grey'
    resolve(delayMS)
    }, delayMS);
    });
    });
    }

    const createLight = (e, color) => {
    if (color !== 'orange') {
    return new Light(e, color)
    }
    const yellowL = new Light(e, color)

    yellowL.turnOn = function() {
    this.isLight = true

    // css 动画
    this.dom.style.transition = 'color'
    this.dom.style.transitionDuration = '.5s'

    const doTurnOn = doPromiseTimeout.bind(this)
    doTurnOn(0)
    .then(() => doTurnOn(1))
    .then(() => doTurnOn(2))
    }
    return yellowL
    }

    var map = new Map()

    for (var i = 0; i < nodeList.length; i++) {
    map.set(nodeList[i], createLight(nodeList[i], colorList[i]))
    }

    document.querySelector('ul').addEventListener('click', e => map.get(e.target).switch())
  1. while promise

    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 createLight = (e, color) => {
    if (color !== 'orange') {
    return new Light(e, color)
    }
    const yellowL = new Light(e, color)

    yellowL.turnOn = function() {
    this.isLight = true

    // css 动画
    this.dom.style.transition = 'color'
    this.dom.style.transitionDuration = '.5s'

    const doTurnOn = doPromiseTimeout.bind(this)
    var blink = Promise.resolve()

    var i = 0;
    while (i < 3) {
    blink = blink.then(() => doTurnOn(i))
    i++
    }

    blink.then(() => {
    this.isLight = false
    })
    }
    return yellowL
    }
  1. 子类 继承
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
// 闪烁类
function Blink(dom, color = "grey", size) {
// 1. 构造器继承 缺点,修改 this 上的方法时,父类的方法也会被修改
Light.call(this, dom, color)
this.size = size
}

/**
* 2. 继承 父类的 Light.prototype ,
* 为什么不使用 new Light() 来进行继承?
* 因为理论上 Light 类实例化的时候时 是需要参数的,这个时候仅仅是继承,传与不传都不太合适
*
*/
Blink.prototype = Object.create(Light.prototype);

/**
* Blink.prototype.constructor === Light true
*
* 3. new Blink(...size) 时,
* size依旧会传进来,对this.size 进行赋值,
* 所以,不写也不会出错,只是有点奇怪,
* 因为指向了 父类的构造器,而不是自己
*/
Blink.prototype.constructor = Blink;

Blink.prototype.blinkOn = function(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
this.dom.style.color = this.color
resolve()
}, ms);
})
}

Blink.prototype.blinkOff = function(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
this.dom.style.color = 'grey'
resolve(ms)
}, ms);
})
}

Blink.prototype.doPromiseTimeout = function(ms) {
return this.blinkOn(ms).then(() => this.blinkOff(500))
}

Blink.prototype.turnOff = function() {
setTimeout(() => {
this.isLight = false
this.dom.style.color = 'grey'
this.dom.style.fontSize = '12px'
}, 500);
}

Blink.prototype.turnOn = function() {
this.isLight = true
this.dom.style.transition = 'color'
this.dom.style.transitionDuration = '.5s'
this.dom.style.fontSize = this.size

let blink = Promise.resolve(1000)

let i = 0;
while (i < 3) {
blink = blink.then((ms) => this.doPromiseTimeout(ms))
i++
}

blink = blink.then(() => this.turnOff())
}
1
2
3
4
5
6
const createLight = (e, color) => {
if (color !== 'orange') {
return new Light(e, color)
}
return new Blink(e, color, '24px')
}
  1. 如何实现一个 promise.all (待完善)
  2. generator yield next (待完善)
  3. async await (待完善)
  4. 实现 bind()

    const bar = foo.bind(this)
    bar()

    1
    2
    3
    4
    5
    6
    Function.prototype.bind = function (ctx) {
    var that = this;
    return function () {
    that.apply(ctx);
    }
    }

    const bar = foo.bind(this)
    bar(arg1, arg2)

    1
    2
    3
    4
    5
    6
    Function.prototype.bind = function(ctx) {
    var that = this;
    return function() {
    return that.apply(ctx, arguments);
    }
    }

    const bar = foo.bind(this, arg1, arg2)
    bar(arg3)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Function.prototype.bind = function (ctx) {
    var that = this;
    var args = Array.prototype.slice.call(arguments, 1);

    return function () {
    var bindArgs = Array.prototype.slice.call(arguments);
    that.apply(ctx, args.concat(bindArgs));
    }
    }

    当作为构造函数时,this 指向实例,that 指向绑定函数,因为下面一句 fbound.prototype = this.prototype;,已经修改了 fbound.prototype 为 绑定函数的 prototype,此时结果为 true,当结果为 true 的时候,this 指向实例。

    当作为普通函数时,this 指向 window,self 指向绑定函数,此时结果为 false,当结果为 false 的时候,this 指向绑定的 ctx。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Function.prototype.bind = function (ctx) {
    var that = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var fNOP = function () {};

    var fbound = function () {
    var bindArgs = Array.prototype.slice.call(arguments);
    that.apply(this instanceof fNOP ? this : ctx, args.concat(bindArgs));
    }
    fNOP.prototype = this.prototype;
    fbound.prototype = new fNOP();
    return fbound;
    }

    e.g.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function Point(x, y) {
    this.x = x;
    this.y = y;
    this.getPoint = function() {
    console.log(this)
    }
    }

    var p = new Point(1, 2);
    p.getPoint(); // '1,2'

    var Point2 = Point.bind2(this, 0);

    var p2 = new Point2(5);
    p2.getPoint(); // '0,5'

    console.log(p2 instanceof Point, p2 instanceof Point2, p instanceof Point2);

    我们直接将 fbound.prototype = this.prototype,我们直接修改 fbound.prototype 的时候,也会直接修改函数的 prototype。so:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Function.prototype.bind = Function.prototype.bind || function (ctx, ...formerArgs) {
    if (typeof this !== 'function') {
    throw new TypeError("NOT_A_FUNCTION -- this is not callable");
    }
    let that = this;
    let fNOP = function () {};
    let args = Array.prototype.slice.call(arguments, 1);

    let fbound = function (...laterArgs) {
    that.apply(this instanceof fNOP ? this : ctx, args.concat(laterArgs));
    };

    if (this.prototype) {
    fNOP.prototype = this.prototype;
    }

    fbound.prototype = new fNOP();
    return fbound;
    };

浅谈js设计模式

发表于 2018-05-11 | nancy | | 阅读次数

策略模式

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。目的是将算法的使用与算法的实现分离开来。

一个基于策略模式的程序至少由两部分组成:

  1. 一组策略类,策略类封装了具体的算法,并且负责具体的计算过程。
  2. 环境类Context, Context接受客户的请求,随后把请求委托给某一个策略类。
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
validator = {
validate: function (value, type) {
switch (type) {
case 'isNonEmpty ':
{
return true; // NonEmpty 验证结果
}
case 'isNumber ':
{
return true; // Number 验证结果
break;
}
case 'isAlphaNum ':
{
return true; // AlphaNum 验证结果
}
default:
{
return true;
}
}
}
};
// 测试
alert(validator.validate("123", "isNonEmpty"));
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
var validator = {

// 所有可以的验证规则处理类存放的地方,后面会单独定义
types: {},

// 验证类型所对应的错误消息
messages: [],

// 当然需要使用的验证类型
config: {},

// 暴露的公开验证方法
// 传入的参数是 key => value对
validate: function (data) {

var i, msg, type, checker, result_ok;

// 清空所有的错误信息
this.messages = [];

for (i in data) {
if (data.hasOwnProperty(i)) {

type = this.config[i]; // 根据key查询是否有存在的验证规则
checker = this.types[type]; // 获取验证规则的验证类

if (!type) {
continue; // 如果验证规则不存在,则不处理
}
if (!checker) { // 如果验证规则类不存在,抛出异常
throw {
name: "ValidationError",
message: "No handler to validate type " + type
};
}

result_ok = checker.validate(data[i]); // 使用查到到的单个验证类进行验证
if (!result_ok) {
msg = "Invalid value for *" + i + "*, " + checker.instructions;
this.messages.push(msg);
}
}
}
return this.hasErrors();
},

// helper
hasErrors: function () {
return this.messages.length !== 0;
}
};





// 验证给定的值是否不为空
validator.types.isNonEmpty = {
validate: function (value) {
return value !== "";
},
instructions: "传入的值不能为空"
};

// 验证给定的值是否是数字
validator.types.isNumber = {
validate: function (value) {
return !isNaN(value);
},
instructions: "传入的值只能是合法的数字,例如:1, 3.14 or 2010"
};

// 验证给定的值是否只是字母或数字
validator.types.isAlphaNum = {
validate: function (value) {
return !/[^a-z0-9]/i.test(value);
},
instructions: "传入的值只能保护字母和数字,不能包含特殊字符"
};



var data = {
first_name: "Tom",
last_name: "Xu",
age: "unknown",
username: "TomXu"
};
//该对象的作用是检查验证类型是否存在
validator.config = {
first_name: 'isNonEmpty',
age: 'isNumber',
username: 'isAlphaNum'
};


validator.validate(data);

if (validator.hasErrors()) {
console.log(validator.messages.join("\n"));
}

通过策略模式,消除了大片的条件分支语句。将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展。同时,算法还可以复用在其他地方,从而避免许多重复的复制粘贴工作。

代理模式

代理模式为一个对象提供一种代理以控制对这个对象的访问。

虚拟代理是我们最常用的代理模式,它把一些开销很大的对象,延迟到真正需要用到这个对象的时候才去创建

虚拟代理实现图片预加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var addImg = (function(){
var img = document.createElement('img');
document.body.appendChild(img);
return {
setSrc: function(src){
img.src = src;
}
}
})();
var proxyAddImg = (function(){
var img = new Image();
img.onload = function(){
addImg.setSrc(this.src);
}
return {
setSrc: function(src){
addImg.setSrc('loading.gif');
img.src = src;
}
}
})();
proxyAddImg.setSrc('demo.png');

虚拟代理合并Http请求
我们可以通过一个代理函数来收集一段时间之内的请求,最后把请求合并到一起发送给服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var proxySynData = (function(){
var cache = [], //缓存我们需要同步的内容
timer; //定时器
return function(ID){
if(!timer){ //定时器不存在就创建
timer = setTimeout(function(){
synData(cache.join()); //同步合并后的数据
cache.length = 0; //清空缓存
clearTimeout(timer); //清除定时器
timer = null; //方便垃圾回收
}, 2000);
}
cache.push(ID); //存入缓存
}
})();
var list = document.getElementsByTagName('input');
for(var i = 0, item; item = list[i]; i++){
item.onclick = function(){
if(this.checked){
proxySynData(this.id);
}
};
}

缓存代理
缓存代理就很好理解了,可以缓存一些开销很大的运算结果;如果你第二次执行函数的时候,传递了同样的参数,那么就直接使用缓存的结果,如果运算量很大,这可是不小的优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var add = function(){
var sum = 0;
for(var i = 0, l = arguments.length; i < l; i++){
sum += arguments[i];
}
return sum;
};
var proxyAdd = (function(){
var cache = {}; //缓存运算结果的缓存对象
return function(){
var args = Array.prototype.join.call(arguments);//把参数用逗号组成一个字符串作为“键”
if(cache.hasOwnProperty(args)){//等价 args in cache
console.log('使用缓存结果');
return cache[args];//直接使用缓存对象的“值”
}
console.log('计算结果');
return cache[args] = add.apply(this,arguments);//使用本体函数计算结果并加入缓存
}
})();
console.log(proxyAdd(1,2,3,4,5)); //15
console.log(proxyAdd(1,2,3,4,5)); //15
console.log(proxyAdd(1,2,3,4,5)); //15

观察者模式

观察者模式又叫发布-订阅模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知。

定义一个事件对象,它有以下功能

  1. 监听事件(订阅公众号)
  2. 触发事件(公众号发布)
  3. 移除事件(取订公众号)
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
78
79
80
81
82
83
84
85
// subscription.js
var CLEARED = null;
var nullListeners = {
notify: function notify() {}
};

function createListenerCollection() {
// the current/next pattern is copied from redux's createStore code.
// TODO: refactor+expose that code to be reusable here?
var current = [];
var next = [];

return {
clear: function clear() {
next = CLEARED;
current = CLEARED;
},
notify: function notify() {
var listeners = current = next;
for (var i = 0; i < listeners.length; i++) {
listeners[i]();
}
},
get: function get() {
return next;
},
subscribe: function subscribe(listener) {
var isSubscribed = true;
if (next === current) next = current.slice();
next.push(listener);

return function unsubscribe() {
if (!isSubscribed || current === CLEARED) return;
isSubscribed = false;

if (next === current) next = current.slice();
next.splice(next.indexOf(listener), 1);
};
}
};
}

var Subscription = function () {
function Subscription(store, parentSub, onStateChange) {
_classCallCheck(this, Subscription);

this.store = store;
this.parentSub = parentSub;
this.onStateChange = onStateChange;
this.unsubscribe = null;
this.listeners = nullListeners;
}

Subscription.prototype.addNestedSub = function addNestedSub(listener) {
this.trySubscribe();
return this.listeners.subscribe(listener);
};

Subscription.prototype.notifyNestedSubs = function notifyNestedSubs() {
this.listeners.notify();
};

Subscription.prototype.isSubscribed = function isSubscribed() {
return Boolean(this.unsubscribe);
};

Subscription.prototype.trySubscribe = function trySubscribe() {
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub ? this.parentSub.addNestedSub(this.onStateChange) : this.store.subscribe(this.onStateChange);

this.listeners = createListenerCollection();
}
};

Subscription.prototype.tryUnsubscribe = function tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe();
this.unsubscribe = null;
this.listeners.clear();
this.listeners = nullListeners;
}
};

return Subscription;
}();

Redux采用了观察者模式

createStore(rootReducer,initialState,applyMiddleware(thunkMiddleware))

返回值:
(1) dispatch(action): 用于action的分发,改变store里面的state
(2) subscribe(listener): 注册listener,store里面state发生改变后,执行该listener。返回unsubscrib()方法,用于注销当前listener。
(3) getState(): 读取store里面的state
(4) replaceReducer(): 替换reducer,改变state修改的逻辑

所以store内部维护listener数组,用于存储所有通过store.subscribe注册的listener;当store tree更新后,依次执行数组中的listener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.');
}

var isSubscribed = true;

ensureCanMutateNextListeners();
nextListeners.push(listener);
了
return function unsubscribe() {
if (!isSubscribed) {
return;
}

isSubscribed = false;

ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
}

dispatch方法主要完成两件事:
1、根据action查询reducer中变更state的方法,更新store tree
2、变更store tree后,依次执行listener中所有响应函数

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
function dispatch(action) {
if (!(0, _isPlainObject2['default'])(action)) {
throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
}

if (typeof action.type === 'undefined') {
throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
}

if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}

try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
//循环遍历,执行listener,通知数据改变
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}

return action;
}

在redux中,我们用connect()方法将react中的UI组件与redux的状态、事件关联起来。

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
var Connect = function (_Component) {
_inherits(Connect, _Component);
/*
* 构造函数中,构造一个订阅对象,属性有this.store,方法this.onStateChange.bind(this)
*/
function Connect(props, context) {
_classCallCheck(this, Connect);

var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));

_this.version = version;
_this.state = {};
_this.renderCount = 0;
_this.store = props[storeKey] || context[storeKey];
_this.propsMode = Boolean(props[storeKey]);
_this.setWrappedInstance = _this.setWrappedInstance.bind(_this);

(0, _invariant2.default)(_this.store, 'Could not find "' + storeKey + '" in either the context or props of ' + ('"' + displayName + '". Either wrap the root component in a <Provider>, ') + ('or explicitly pass "' + storeKey + '" as a prop to "' + displayName + '".'));

_this.initSelector();
_this.initSubscription();
return _this;
}

···
// 调用store.subscribe(listener)注册监听方法,对store的变化进行订阅,当store变化的时候,更新渲染view
Connect.prototype.componentDidMount = function componentDidMount() {
if (!shouldHandleStateChanges) return;
this.subscription.trySubscribe(); // //实际调用this.store.subscribe(this.onStateChange);
this.selector.run(this.props);
if (this.selector.shouldComponentUpdate) this.forceUpdate();
};
···

Connect.prototype.componentWillUnmount = function componentWillUnmount() {
if (this.subscription) this.subscription.tryUnsubscribe(); // 取消订阅
this.subscription = null;
this.notifyNestedSubs = noop;
this.store = null;
this.selector.run = noop;
this.selector.shouldComponentUpdate = false;
};
···
//初始化订阅逻辑
Connect.prototype.initSubscription = function initSubscription() {
if (!shouldHandleStateChanges) return;

var parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey];
this.subscription = new _Subscription2.default(this.store, parentSub, this.onStateChange.bind(this)); //调用的是Subscription.js中方法,向store内部注册一个listener---this.onStateChange.bind(this)

this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription);
};

Connect.prototype.onStateChange = function onStateChange() {
this.selector.run(this.props);

if (!this.selector.shouldComponentUpdate) {
this.notifyNestedSubs();
} else {
this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate;
this.setState(dummyState);
}
};

···

return Connect;
}(_react.Component);

装饰者模式

在不改变对象自身的基础上,在程序运行期间给对象动态地添加一些额外职责

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 给window绑定onload事件
window.onload = function() {
alert(1)
}

var _onload = window.onload || function(){} //维护中间变量,但是如果装饰链太长,或需要的装饰函数太多,这些中间变量的数量就会越来越多

window.onload = function() {
_onload()
alert(2)
}

//可能会存在this被劫持问题
var getId = document.getElementById; //全局函数,this指向window
document.getElementById = function(ID){
console.log(1);
return getId(ID);
}
document.getElementById('demo'); //this预期指向document

//需要手动把document当做上下文this传入getId

AOP装饰函数

AOP(Aspect Oriented Programming)面向切面编程
把一些与核心业务逻辑无关的功能抽离出来
再通过“动态织入”方式掺入业务逻辑模块

1
2
3
4
5
6
7
8
9
10
11
12
// 前置装饰
Function.prototype.before = function(beforeFunc){
var that = this; //保存原函数的引用
return function(){ //返回了了包含原函数和新函数的代理函数
beforeFunc.apply(this, arguments); // 执行新函数
return that.apply(this, arguments); //执行原函数并返回原函数的执行结果
}
}

document.getElementById = document.getElementById.before(function() {
alet(1)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
//定义一个组件
class Home extends Component {
//....
}

//以往从状态树取出对应的数据,通过props传给组件使用通过react-redux自带的connect()方法
export default connect(state => ({todos: state.todos}))(Home);

//使用装饰器的话就变成这样,好像没那么复杂
@connect(state => ({ todos: state.todos }))
class Home extends React.Component {
//....
}

适配器模式

适配器模式是作为两个不兼容的接口之间的桥梁,它结合了两个独立接口的功能。

假设我们正在编写一个渲染广东省地图的页面,目前从第三方资源里获得了广东省的所有城市以及它们所对应的ID,并且成功地渲染到页面中:

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
var getGuangdongCity = function () {
var GuangdongCity = [
{
name:'shenzhen',
id : '11'
},
{
name:'guangzhou',
id:12
}
];
return GuangdongCity;
};

var render = function (fn) {
console.log('starting render Guangdong map');
document.write(JSON.stringify(fn()));
};
/* var GuangdongCity = {
// shenzhen:11,
// guangzhou:12,
// zhuhai:13
// };
*/
var addressAdapter = function (oldAddressfn) {
var address = {},
oldAddress = oldAddressfn();
for(var i = 0 , c; c = oldAddress[i++];){
address[c.name] = c.id; //此处我们遍历老数据把它们添加到空对象中然后返回这个对象
}
return function () {
return address;
}
};
render(addressAdapter(getGuangdongCity));

使用适配器模式可以解决参数类型有些许不一致造成的问题。

redux为了和react适配,所有有 mapStateToProps()这个函数来把state转为Props外部状态,这样就可以从外部又回到组件内了

二叉树那些事儿

发表于 2018-05-11 | nancy | | 阅读次数

大家在聊到二叉树的时候,总会离不开链表。这里先带大家一起了解一些基本概念。

线性表

概念

线性表是最基本、最简单、也是最常用的一种数据结构。

线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的(注意,这句话只适用大部分线性表,而不是全部。比如,循环链表逻辑层次上也是一种线性表(存储层次上属于链式存储),但是把最后一个数据元素的尾指针指向了首位结点)。

我们说“线性”和“非线性”,只在逻辑层次上讨论,而不考虑存储层次,所以双向链表和循环链表依旧是线性表。在数据结构逻辑层次上细分,线性表可分为一般线性表和受限线性表。一般线性表也就是我们通常所说的“线性表”,可以自由的删除或添加结点。受限线性表主要包括栈和队列,受限表示对结点的操作受限制。

特征

1.集合中必存在唯一的一个“第一元素”。
2.集合中必存在唯一的一个 “最后元素” 。
3.除最后一个元素之外,均有 唯一的后继(后件)。
4.除第一个元素之外,均有 唯一的前驱(前件)。

存储结构

线性表主要由顺序表示或链式表示。在实际应用中,常以栈、队列、字符串等特殊形式使用。

顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,称为线性表的顺序存储结构或顺序映像。它以“物理位置相邻”来表示线性表中数据元素间的逻辑关系,可随机存取表中任一元素。

链式表示指的是用一组任意的存储单元存储线性表中的数据元素,称为线性表的链式存储结构。它的存储单元可以是连续的,也可以是不连续的。在表示数据元素之间的逻辑关系时,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置),这两部分信息组成数据元素的存储映像,称为结点(node)。它包括两个域;存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。指针域中存储的信息称为指针或链

链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。

每个结点包括两个部分:

  1. 存储数据元素的数据域
  2. 存储下一个结点地址的指针域。
    链表不必须按顺序存储,链表在插入的时候可以达到O(1)的时间复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
    链表结点声明如下:
    1
    2
    3
    4
    5
    6
    class ListNode
    {
    let nodeValue; //数据
    let next; //指向下一个节点的指针

    };

从内存角度出发: 链表可分为 静态链表、动态链表。
从链表存储方式的角度出发:链表可分为 单向链表、双向链表、以及循环链表。

静态链表

把线性表的元素存放在数组中,这些元素可能在物理上是连续存放的,也有可能不是连续的,它们之间通过逻辑关系来连接,数组单位存放链表结点,结点的链域指向下一个元素的位置,即下一个元素所在的数组单元的下标。显然静态链表需要数组来实现。
引出的问题:数组的长度定义的问题,无法预支。

动态链表(实际当中用的最多)

改善了静态链表的缺点。它动态的为节点分配存储单元。当有节点插入时,系统动态的为结点分配空间。在结点删除时,应该及时释放相应的存储单元,以防止内存泄露。

单链表

单链表是一种顺序存储的结构。有一个头结点,没有值域,只有连域,专门存放第一个结点的地址。有一个尾结点,有值域,也有链域,链域值始终为NULL。所以,在单链表中为找第i个结点或数据元素,必须先找到第i - 1 结点或数据元素,而且必须知道头结点,否者整个链表无法访问。

循环链表

循环链表,类似于单链表,也是一种链式存储结构,循环链表由单链表演化过来。单链表的最后一个结点的链域指向NULL,而循环链表的建立,不要专门的头结点,让最后一个结点的链域指向链表结点。

循环链表与单链表的区别

  1. 链表的建立。单链表需要创建一个头结点,专门存放第一个结点的地址。单链表的链域指向NULL。而循环链表的建立,不要专门的头结点,让最后一个结点的链域指向链表的头结点。
  2. 链表表尾的判断。单链表判断结点是否为表尾结点,只需判断结点的链域值是否是NULL。如果是,则为尾结点;否则不是。而循环链表盘判断是否为尾结点,则是判断该节点的链域是不是指向链表的头结点。

双链表

双链表也是基于单链表的,单链表是单向的,有一个头结点,一个尾结点,要访问任何结点,都必须知道头结点,不能逆着进行。而双链表则是添加了一个链域。通过两个链域,分别指向结点的前结点和后结点。这样的话,可以通过双链表的任何结点,访问到它的前结点和后结点。但是双链表还是不够灵活,在实际编程中比较常用的是循环双链表。但循环双链表使用较为麻烦。

img_1

链表相关题目

求单链表中结点的个数
这是最最基本的了,应该能够迅速写出正确的代码,注意检查链表是否为空。时间复杂度为O(n)。参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
function GetListLength(head)  
{
if(head === NULL) return 0;
let len = 0;
let current = head; // ListNode
while(current !== NULL)
{
len++;
current = current.next;
}
return len;
}

将单链表反转
从头到尾遍历原链表,每遍历一个结点,将其摘下放在新链表的最前端。注意链表为空和只有一个结点的情况。时间复杂度为O(n)。参考代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 反转单链表  
function ReverseList(ListNode head)
{
// 如果链表为空或只有一个结点,无需反转,直接返回原链表头指针
if(head === NULL || head.next === NULL) return head;
let prev = NULL; // ListNode 反转后的新链表头指针,初始为NULL
let current = head;
while(current !== NULL)
{
let temp = current; //获取当前节点ListNode
current = current.next; //获取下一个节点
temp.next = prev; // 将当前结点摘下,插入新链表的最前端
prev = temp; //上一个节点前进一位
}
return prev;
}

已知两个单链表pHead1 和pHead2 各自有序,把它们合并成一个链表依然有序
这个类似归并排序。尤其注意两个链表都为空,和其中一个为空时的情况。只需要O(1)的空间。时间复杂度为O(max(len1, len2))。参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 合并两个有序链表  
function MergeSortedList(head1, head2)
{
if(head1 == NULL) return head2;
if(head2 == NULL) return head1;
let headMerged = NULL;
if(head1.nodeValue < head2.nodeValue)
{
headMerged = head1;
headMerged.next = MergeSortedList(head1.next, head2);
}
else
{
headMerged = head2;
headMerged.next = MergeSortedList(head1, head2.next);
}
return headMerged;
}

判断一个单链表中是否有环
这里也是用到两个指针。如果一个链表中有环,也就是说用一个指针去遍历,是永远走不到头的。因此,我们可以用两个指针去遍历,一个指针一次走两步,一个指针一次走一步,如果有环,两个指针肯定会在环中相遇。时间复杂度为O(n)。参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
function HasCircle(head)  
{
let pFast = head; // 快指针每次前进两步
let pSlow = head; // 慢指针每次前进一步
while(pFast != NULL && pFast.next != NULL)
{
pFast = pFast.next.next;
pSlow = pSlow.next;
if(pSlow == pFast) // 相遇,存在环
return true;
}
return false;
}

判断两个单链表是否相交
如果两个链表相交于某一节点,那么在这个相交节点之后的所有节点都是两个链表所共有的。也就是说,如果两个链表相交,那么最后一个节点肯定是共有的。
先遍历第一个链表,记住最后一个节点,然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则不相交。时间复杂度为O(len1+len2),因为只需要一个额外指针保存最后一个节点地址,空间复杂度为O(1)。参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function IsIntersected(head1, head2)  
{
if(pHead1 == NULL || pHead2 == NULL) return false;
let pTail1 = head1; // 用来存储第一个链表的最后一个节点
while(pTail1.next != NULL) {
pTail1 = pTail1.next;
}

let pTail2 = head2; // 用来存储第二个链表的最后一个节点
while(pTail2.next != NULL) {
pTail2 = pTail2.next;
}
return pTail1 == pTail2;
}

查找单链表中的倒数第K个结点(k > 0)
主要思路就是使用两个指针,先让前面的指针走到正向第k个结点,这样前后两个指针的距离差是k-1,之后前后两个指针一起向前走,前面的指针走到最后一个结点时,后面指针所指结点就是倒数第k个结点。
参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 查找单链表中倒数第K个结点  
function GetRKthNode(head, k)
{
if(k == 0 || head == NULL) // 这里k的计数是从1开始的,若k为0或链表为空返回NULL
return NULL;

let pAhead = head;
let pBehind = head;
while(k > 1 && pAhead != NULL) // 前面的指针先走到正向第k个结点
{
pAhead = pAhead.next;
k--;
}
if(k > 1 || pAhead == NULL) // 结点个数小于k,返回NULL
return NULL;
while(pAhead.next != NULL) // 前后两个指针一起向前走,直到前面的指针指向最后一个结点
{
pBehind = pBehind.next;
pAhead = pAhead.next;
}
return pBehind; // 后面的指针所指结点就是倒数第k个结点
}

查找单链表的中间结点
此题可应用于上一题类似的思想。也是设置两个指针,只不过这里是,两个指针同时向前走,前面的指针每次走两步,后面的指针每次走一步,前面的指针走到最后一个结点时,后面的指针所指结点就是中间结点,即第(n/2+1)个结点。注意链表为空,链表结点个数为1和2的情况。时间复杂度O(n)。参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取单链表中间结点,若链表长度为n(n>0),则返回第n/2+1个结点  
function GetMiddleNode(head)
{
if(head == NULL || head.next == NULL) // 链表为空或只有一个结点,返回头指针
return head;

let pAhead = head;
let pBehind = head;
while(pAhead.next != NULL) // 前面指针每次走两步,直到指向最后一个结点,后面指针每次走一步
{
pAhead = pAhead.next;
pBehind = pBehind.next;
if(pAhead.next != NULL)
pAhead = pAhead.next;
}
return pBehind; // 后面的指针所指结点即为中间结点
}

反转双向链表

1
2
3
4
5
6
7
8
9
10
function reverse(head){
let cur = null;
while(head != null){
cur = head;
head = head.next;
cur.next = cur.prev;
cur.prev = head;
}
return cur;
}

二叉树

概念

二叉树(Binary Tree)是n(n>=0)个结点的有限集合,该集合或者为空集(空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

特点

每个结点最多有两棵子树,所以二叉树中不存在大于2的结点。二叉树中每一个节点都是一个对象,每一个数据节点都有三个指针,分别是指向父母、左孩子和右孩子的指针。每一个节点都是通过指针相互连接的。相连指针的关系都是父子关系。

二叉树节点的定义

1
2
3
4
5
6
struct BinaryTreeNode
{
int m_nValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
};

二叉树的五种基本形态

img_2

满二叉树

在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。如下图所示:
img_3

完全二叉树

完全二叉树是指最后一层左边是满的,右边可能满也可能不满,然后其余层都是满的。一个深度为k,节点个数为 2^k - 1 的二叉树为满二叉树(完全二叉树)。就是一棵树,深度为k,并且没有空位。

完全二叉树的特点有:

  1. 叶子结点只能出现在最下两层。

  2. 最下层的叶子一定集中在左部连续位置。

  3. 倒数第二层,若有叶子结点,一定都在右部连续位置。

  4. 如果结点度为1,则该结点只有左孩子。

  5. 同样结点树的二叉树,完全二叉树的深度最小。

img_4

二叉树的遍历

是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。

二叉树的遍历有三种方式,如下:
前序遍历:
若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
中序遍历:
若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
后序遍历:
若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后访问根结点。

二叉查找树

二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
(3)左、右子树也分别为二叉排序树;

二叉查找树(BST)由节点组成,所以我们定义一个Node节点对象如下:

1
2
3
4
5
6
7
8
9
10
11
function Node(data,left,right){
this.data = data;
this.left = left;//保存left节点链接
this.right = right;
this.show = show;
}


function show(){
return this.data;//显示保存在节点中的数据
}

查找最大和最小值
查找BST上的最小值和最大值非常简单,因为较小的值总是在左子节点上,在BST上查找最小值,只需遍历左子树,直到找到最后一个节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getMin(){
var current = this.root;
while(!(current.left == null)){
current = current.left;
}
return current.data;
}

function getMax(){
var current = this.root;
while(!(current.right == null)){
current = current.right;
}
return current.data;
}

二叉树相关题目

求二叉树中的节点个数

递归解法:
(1)如果二叉树为空,节点个数为0
(2)如果二叉树不为空,二叉树节点个数 = 左子树节点个数 + 右子树节点个数 + 1
参考代码如下:

1
2
3
4
5
function GetNodeNum(pRoot)  
{
if(pRoot == NULL) return 0;
return GetNodeNum(pRoot->m_pLeft) + GetNodeNum(pRoot->m_pRight) + 1;
}

求二叉树的深度

递归解法:
(1)如果二叉树为空,二叉树的深度为0
(2)如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1
参考代码如下:

1
2
3
4
5
6
7
function GetDepth(pRoot)  
{
if(pRoot == NULL) return 0;
let depthLeft = GetDepth(pRoot->m_pLeft);
let depthRight = GetDepth(pRoot->m_pRight);
return depthLeft > depthRight ? (depthLeft + 1) : (depthRight + 1);
}

前序遍历,中序遍历,后序遍历

前序遍历递归解法:
(1)如果二叉树为空,空操作
(2)如果二叉树不为空,访问根节点,前序遍历左子树,前序遍历右子树
参考代码如下:

1
2
3
4
5
6
7
function PreOrderTraverse(pRoot)  
{
if(pRoot == NULL) return;
Visit(pRoot); // 访问根节点
PreOrderTraverse(pRoot->m_pLeft); // 前序遍历左子树
PreOrderTraverse(pRoot->m_pRight); // 前序遍历右子树
}

中序遍历递归解法
(1)如果二叉树为空,空操作。
(2)如果二叉树不为空,中序遍历左子树,访问根节点,中序遍历右子树
参考代码如下:

1
2
3
4
5
6
7
function InOrderTraverse(pRoot)  
{
if(pRoot == NULL) return;
InOrderTraverse(pRoot->m_pLeft); // 中序遍历左子树
Visit(pRoot); // 访问根节点
InOrderTraverse(pRoot->m_pRight); // 中序遍历右子树
}

后序遍历递归解法
(1)如果二叉树为空,空操作
(2)如果二叉树不为空,后序遍历左子树,后序遍历右子树,访问根节点
参考代码如下:

1
2
3
4
5
6
7
function PostOrderTraverse(pRoot)  
{
if(pRoot == NULL) return;
PostOrderTraverse(pRoot->m_pLeft); // 后序遍历左子树
PostOrderTraverse(pRoot->m_pRight); // 后序遍历右子树
Visit(pRoot); // 访问根节点
}

分层遍历二叉树(按层次从上往下,从左往右)

相当于广度优先搜索,使用队列实现。队列初始化,将根节点压入队列。当队列不为空,进行如下操作:弹出一个节点,访问,若左子节点或右子节点不为空,将其压入队列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function LevelTraverse(pRoot)  
{
if(pRoot == NULL) return;
queue<BinaryTreeNode *> q;
q.push(pRoot);
while(!q.empty())
{
BinaryTreeNode * pNode = q.front();
q.pop();
Visit(pNode); // 访问节点
if(pNode->m_pLeft != NULL)
q.push(pNode->m_pLeft);
if(pNode->m_pRight != NULL)
q.push(pNode->m_pRight);
}
return;
}

将二叉查找树变为有序的双向链表

要求不能创建新节点,只调整指针。
递归解法:
(1)如果二叉树查找树为空,不需要转换,对应双向链表的第一个节点是NULL,最后一个节点是NULL
(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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*******************************************************************
参数:
pRoot: 二叉查找树根节点指针
pFirstNode: 转换后双向有序链表的第一个节点指针
pLastNode: 转换后双向有序链表的最后一个节点指针
*******************************************************************/
function Convert(BinaryTreeNode * pRoot,
BinaryTreeNode * & pFirstNode, BinaryTreeNode * & pLastNode)
{
BinaryTreeNode *pFirstLeft, *pLastLeft, * pFirstRight, *pLastRight;
if(pRoot == NULL)
{
pFirstNode = NULL;
pLastNode = NULL;
return;
}

if(pRoot->m_pLeft == NULL)
{
// 如果左子树为空,对应双向有序链表的第一个节点是根节点
pFirstNode = pRoot;
}
else
{
Convert(pRoot->m_pLeft, pFirstLeft, pLastLeft);
// 二叉查找树对应双向有序链表的第一个节点就是左子树转换后双向有序链表的第一个节点
pFirstNode = pFirstLeft;
// 将根节点和左子树转换后的双向有序链表的最后一个节点连接
pRoot->m_pLeft = pLastLeft;
pLastLeft->m_pRight = pRoot;
}

if(pRoot->m_pRight == NULL)
{
// 对应双向有序链表的最后一个节点是根节点
pLastNode = pRoot;
}
else
{
Convert(pRoot->m_pRight, pFirstRight, pLastRight);
// 对应双向有序链表的最后一个节点就是右子树转换后双向有序链表的最后一个节点
pLastNode = pLastRight;
// 将根节点和右子树转换后的双向有序链表的第一个节点连接
pRoot->m_pRight = pFirstRight;
pFirstRight->m_pLeft = pRoot;
}

return;
}

求二叉树第K层的节点个数

递归解法:
(1)如果二叉树为空或者k<1返回0
(2)如果二叉树不为空并且k==1,返回1
(3)如果二叉树不为空且k>1,返回左子树中k-1层的节点个数与右子树k-1层节点个数之和
参考代码如下:

1
2
3
4
5
6
7
8
function GetNodeNumKthLevel(pRoot, k)  
{
if(pRoot == NULL || k < 1) return 0;
if(k == 1) return 1;
let numLeft = GetNodeNumKthLevel(pRoot->m_pLeft, k-1); // 左子树中k-1层的节点个数
let numRight = GetNodeNumKthLevel(pRoot->m_pRight, k-1); // 右子树中k-1层的节点个数
return (numLeft + numRight);
}

判断两棵二叉树是否结构相同

不考虑数据内容。结构相同意味着对应的左子树和对应的右子树都结构相同。
递归解法:
(1)如果两棵二叉树都为空,返回真
(2)如果两棵二叉树一棵为空,另一棵不为空,返回假
(3)如果两棵二叉树都不为空,如果对应的左子树和右子树都同构返回真,其他返回假
参考代码如下:

1
2
3
4
5
6
7
8
9
10
function StructureCmp(BinaryTreeNode * pRoot1, BinaryTreeNode * pRoot2)  
{
if(pRoot1 == NULL && pRoot2 == NULL) // 都为空,返回真
return true;
else if(pRoot1 == NULL || pRoot2 == NULL) // 有一个为空,一个不为空,返回假
return false;
let resultLeft = StructureCmp(pRoot1->m_pLeft, pRoot2->m_pLeft); // 比较对应左子树
let resultRight = StructureCmp(pRoot1->m_pRight, pRoot2->m_pRight); // 比较对应右子树
return (resultLeft && resultRight);
}

判断二叉树是不是平衡二叉树

递归解法:
(1)如果二叉树为空,返回真
(2)如果二叉树不为空,如果左子树和右子树都是AVL树并且左子树和右子树高度相差不大于1,返回真,其他返回假

求二叉树的镜像

递归解法:
(1)如果二叉树为空,返回空
(2)如果二叉树不为空,求左子树和右子树的镜像,然后交换左子树和右子树
参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
function Mirror(BinaryTreeNode * pRoot)  
{
if(pRoot == NULL) // 返回NULL
return NULL;
BinaryTreeNode * pLeft = Mirror(pRoot->m_pLeft); // 求左子树镜像
BinaryTreeNode * pRight = Mirror(pRoot->m_pRight); // 求右子树镜像
// 交换左子树和右子树
pRoot->m_pLeft = pRight;
pRoot->m_pRight = pLeft;
return pRoot;
}

判断二叉树是不是完全二叉树

若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
有如下算法,按层次(从上到下,从左到右)遍历二叉树,当遇到一个节点的左子树为空时,则该节点右子树必须为空,且后面遍历的节点左右子树都必须为空,否则不是完全二叉树。

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
bool IsCompleteBinaryTree(BinaryTreeNode * pRoot)  
{
if(pRoot == NULL)
return false;
queue<BinaryTreeNode *> q;
q.push(pRoot);
bool mustHaveNoChild = false;
bool result = true;
while(!q.empty())
{
BinaryTreeNode * pNode = q.front();
q.pop();
if(mustHaveNoChild) // 已经出现了有空子树的节点了,后面出现的必须为叶节点(左右子树都为空)
{
if(pNode->m_pLeft != NULL || pNode->m_pRight != NULL)
{
result = false;
break;
}
}
else
{
if(pNode->m_pLeft != NULL && pNode->m_pRight != NULL)
{
q.push(pNode->m_pLeft);
q.push(pNode->m_pRight);
}
else if(pNode->m_pLeft != NULL && pNode->m_pRight == NULL)
{
mustHaveNoChild = true;
q.push(pNode->m_pLeft);
}
else if(pNode->m_pLeft == NULL && pNode->m_pRight != NULL)
{
result = false;
break;
}
else
{
mustHaveNoChild = true;
}
}
}
return result;
}

前端分享

发表于 2018-05-08 | fjcgreat | | 阅读次数

CSS伪元素和伪类

1. 常见的伪元素和伪类

  • 常见的伪类 :focus :hover :active :visited :link :first-child :lang
  • 常见的伪元素(伪对象) :before :after :first-letter :first-line ::placeholder

2. 伪类

CSS3中对伪类的定义:

  • 伪类存在的意义是为了通过选择器找到那些不存在与DOM树中的信息以及不能被常规CSS选择器获取到的信息
  • 伪类由一个冒号:开头,冒号后面是伪类的名称和包含在圆括号中的可选参数
  • 任何常规选择器可以再任何位置使用伪类。伪类语法不区别大小写。一些伪类的作用会互斥,另外一些伪类可以同时被同一个元素使用。并且,为了满足用户在操作DOM时产生的DOM结构改变,伪类也可以是动态的

CSS3 新添加了一些伪类

3. 伪元素

CSS3中对伪元素的定义:

  • 伪元素在DOM树中创建了一些抽象元素,这些抽象元素是不存在于文档语言里的(可以理解为html源码)。比如:document接口不提供访问元素内容的第一个字或者第一行的机制,而伪元素可以使开发者可以提取到这些信息。并且,一些伪元素可以使开发者获取到不存在于源文档中的内容(比如常见的::before, ::after)
  • 伪元素的由两个冒号::开头,然后是伪元素的名称
  • 使用两个冒号::是为了区别伪类和伪元素(CSS2中并没有区别)。考虑到兼容性,CSS2中已存的伪元素仍然可以使用一个冒号:的语法,但是CSS3中新增的伪元素必须使用两个冒号::
  • 一个选择器只能使用一个伪元素,并且伪元素必须处于选择器语句的最后

4. 伪类和伪元素的区别

伪类和伪元素是CSS1和CSS2提出的概念,前两个阶段区分模糊,在CSS3中对这两个概念做了相对较清晰地概念,并且在语法上也很明显的将二者区别开。

  • 伪类本质上是为了弥补常规CSS选择器的不足,以便获取到更多信息;
  • 伪元素本质上是创建了一个有内容的虚拟容器;
  • CSS3中伪类和伪元素的语法不同;
  • 可以同时使用多个伪类,而只能同时使用一个伪元素 , 并且只能出现在末尾
  • 伪类与类优先级相同
  • 伪元素与标签优先级相同

为什么0.1 + 0.2 !== 0.3,如何保证这种判等是对的

因为JavaScript 使用IEEE754 (64位双精度)二进制浮点数计算标准;JavaScript中Number 都是按照这个标准存储的。

IEEE754 (32位双精度)存储数字时,从左到右,1位符号,8位指数位(阶码),23位有效数位(尾数)
IEEE754 (64位双精度)是 1位符号,11位指数位(阶码),52位有效数位(尾数)

0.1 转为二进制数时 是0.00011(0011),0011 部分无限循环,所以0.1的二进制在存储时,最后就会涉及到进位;0.2在转换时也是无限循环的。
所以 0.1 + 0.2 = 0.30000000000000004 而不是 0.3

解决方法:

  1. 不考虑精度
    code_1
  2. 使用ES6提供的Number.EPSILON (机器精度)
    code_2

列举字符串转数字的方法

1
2
3
4
5
6
7
8
Number构造函数
parseInt
parseFloat
+"1"
"1"*1
"1"|0
"1" >> 0
"1" << 0

mac及vscode常用快键键

发表于 2017-11-06 | 布谷 | 分类于 tools | | 阅读次数

mac快捷键

key action
option + 点击wifi图标 显示ip
长按最大化按钮 分屏
设置 -> 辅助功能 -> 鼠标与触控板 -> 触控板选项 启用三指拖移

vscode快捷键

key action
option + shift + f 格式化代码
command + k + f 格式化代码
control + - 回到上一次光标位置
option + shift + ↓ 向下复制行

vscode插件

插件 用途
Prettier 根据eslint格式化代码
Search node_modules 快速查找node_modules

行内元素垂直方向布局终极解密

发表于 2017-08-22 | yoution | 分类于 browser | | 阅读次数

行内元素垂直方向layout终极揭秘

本文主要研究 inlineElement 在垂直方向的layout过程。
只探究单层嵌套,不考虑anoymousElement,不考虑overflow情况,不考虑outOfFlow情况。
inlineBox
inlineElement的layout过程主要为了确定span和span中字符的left,top,width,height的值,即inlineFlowBox和inlineTextBox的left,top,width,height的值。
对于LTR的布局,父元素blockElement在包含子元素inlineElement的时候,layout以行为单位,首先进行水平方向的布局,再进行垂直方向的布局。
水平方向以inlineFlowBox为单位,累计计算每个字符的宽度,判断换行位置,确定当前行所能容纳的字符的inlineFlowBox,确定每个inlineFlowBox的left值, 水平方向left计算除font本身外,还受margin,border,padding,word-wrap,word-break,white-space,word-spacing,hanging-punctuation影响;
垂直方向主要确定每行各个inlineFlowBox在rootInlineBox中的top值,inlineFlowBox通过vertical-align值,确定各自的top值,inlineFlowBox的top值受border,padding影响,不受margin影响,inlineTextBox不受margin,border,padding影响。

inlineTextBox介绍

inlineElement在layout的时候会生成inlineFlowBox,对于内部的字符,会生成inlineTextBox, 对于非anoymousElement,设置border就可以看到inlineElement的inlineFlowBox的长和宽。
字符的各项参数glaph metrics:
font-metrics

  • ascent: inlineTextBox的上部分,字符最高处与ascent顶端可能有空白,由font-family决定
  • descent: inlineTextBox的下部分,字符最低处与descent底端可能有空白,由font-family决定
  • baseline: 分隔ascent和descent,字符底端沿baseline排列,如图中的P,x,E(为俄文字符)
  • xHeight: 小写字符x的高度,由font-family决定
  • capHeight: 大些字符P的高度,由font-family决定
  • lineSpacing: lineGap + ascent + descent + adjustment,本文测试了很多情况,lineGap 都为0, adjustment 是在ios平台对于特殊字体的一个调整,所以本文暂且认为lineSpacing = ascent + descent
  • lineHeight:默认等于lineSpacing,受line-height设置影响,如果设置line-height,lineHeight等于line-height,lineHeight与lineSpacing之间会上下相等的空隙 (lineHeight-lineSpacing) / 2;

ascent, descent, xHeight, capHeight为font自生属性,与font-family有关,设置相同line-height和font-size,对于不同的font-family,ascent,descent,xHeight,capHeight值都不同:
font-family
ascent,descent,lineHeight,xHeight,capHeight虽然与font-family有关,但是对于不同的系统平台,对于某些font-family,ascent,descent,lineHeight,xHeight,capHeight的值会特殊处理。

垂直方向的layout

line-layout
垂直方向的layout以行为单位,确定inlineFlowBox和inlineTextBox的top值,对于rootInineBox每行会有一条类似inlineTextBox的baseline的线,该行的高度由该线的上端maxAscent和下端maxDecent组成,maxAscent和maxDecent由当前行内的inlineTextBox决定。

vertical-height的属性

inlineTextBox的top有以下10个值:

  • baseline
  • middle
  • sub
  • super
  • text-top
  • text-bottom
  • top
  • bottom
  • baseline_middle
  • length(数值|百分比)

设置inlineFlowBox的logicalTop

logicalTop是layout过程中设置的中间值,layout过程会持续设置logicalTop的值,直到layout结束,最终的logicalTop即为top值,对于不同的vertical-align值,inlineFlowBox的logicalTop初始值为:

  • baseline: 0
  • middle: -rootInlineBox的xHeight / 2 - lineHeight / 2 + baselinePosition
  • sub: +rootInlineBox的fontSize / 5 + 1
  • super: -rootInlineBox的fontSize / 3 + 1
  • text-top: +baselinePosition - rootInlineBox的ascent
  • top: 0
  • bottom: 0
  • text-bottom: 对于replaceElement: +rootInlineBox的descent;对于noreplaceElement: +rootInlineBox + descent - lineHeight + baselinePosition
  • baseline_middle: -lineHeight / 2 + baselinePosition
  • length: 数值:-value;百分比:-lineHeight * value

其中baselinePosition = ascent + (lineHeight - lineSpaceing) / 2

rootInlineBox的maxAscent和maxDescent的确定

  • 当inlineFlowBox的vertical-align为top ,rootInlineBox的maxPositionTop = ascent + descent
  • 当inlineFlowBox的vertical-align为bottom, rootInlineBox的maxPositionBottom = ascent + descent
  • 当inlineFlowBox的vertical-align为其他情况,maxAscent = max(ascent - LogicalTop, ascent - LogicalTop, …),maxDescent = max(descent + LogicalTop, descent + LogicalTop, …)

如果 maxAscent + maxDecent < max(maxPositionTop, maxPositionBottom),调整maxAscent和maxDescent

  • 如果inlineFlowBox的vertical-align为top,maxAscent + maxAscent < lineHeight,maxDescent = lineHeight- maxAscent
  • 如果inlineFlowBox的vertical-align为bottom,maxAscent + maxAscent < lineHeight,maxAscent = lineHeight- maxDescent

如果遍历inlineFlowBox满足maxAscent + maxAscent >= max(maxPositionTop, maxPositionBottom),退出调整,则rootInlineBox的maxHeight = maxAscent + maxDecent

确定inlineFlowBox的top值

在当前layout过程,父元素rootInlineBox的logicalHeight = broderTop + paddingTop,在整个文档layout的文章中会介绍;再次计算inlineFlowBox的logicalTop值,进而确定inlineTexBox的logicalTop值。

  • 当inlineFlowBox的vertical-align为top,logicalTop = rootInlineBox的logicalHeight
  • 当inlineFlowBox的vertical-align为bottom ,logicalTop = rootInlineBox的logicalHeight + rootInlineBox的maxHeight - lineHeight
  • 当inlineFlowBox的vertical-align 为其他情况,logicalTop += rootInlineBox的logicalTop + rootInlineBox的maxAscent - baselinePosition

最终logicalTop += baselinePosition - ascent - borderTop - paddingTop。

确定inlineTextBox的top值

设置inlineTextBox的logicalTop,logicalTop = inlineFlowBox的logicalTop + inlineFlowBox的(paddingTop + borderTop)

设置rootLineBox的logicalHeight

logicalHeight += maxAscent + maxDecent,当前行layout结束,继续下一行layout。

参考文档:

  • https://developer.mozilla.org/en-US/docs/Web/CSS/Replaced_element
  • https://stackoverflow.com/questions/41336177/font-size-vs-line-height-vs-actual-height
  • https://stackoverflow.com/questions/3654321/measuring-text-height-to-be-drawn-on-canvas-android/42091739#42091739
  • https://stackoverflow.com/questions/27631736/meaning-of-top-ascent-baseline-descent-bottom-and-leading-in-androids-font
  • https://docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-obtain-font-metrics
  • https://github.com/soulwire/FontMetrics
  • https://soulwire.github.io/FontMetrics/
  • https://typedecon.com/blogs/type-glossary

如果你觉得阅读本文有收获,请给我打赏,谢谢

Javascript各种类型的加载与执行

发表于 2017-07-12 | yoution | 分类于 browser | | 阅读次数

Javascript 各种类型的加载与执行顺序

  1. script src + defer

    1
    <script src="xx" defer></script>

    是否阻塞parse: 不阻塞
    执行时机:parse结束后待js文件全部加载完成后,按照script出现的顺序执行,全部执行完触发DOMContentLoaded事件

  2. script type=module + src

    1
    <script type=module src='xx'></script>

    同1

  3. script type=module + 代码块

    1
    2
    3
    <script type='module'>
    code
    </script>

    同1,虽然代码已经在本地,还是会在下个时间点的下个时间点执行

  4. script src

    1
    <script src="xx"></script>

    是否阻塞parse: 阻塞, 停止parse,但是会触发preLoad
    执行时机:停止parse,等待js文件加载完成后,如果前面没有css文件加载,立即执行js,恢复parse;如果前面有css文件加载,则等待css加载完毕,解析css完成后,立即执行js,恢复parse。preLoad 的css 的加载不会影响到js

  5. script src + async

    1
    <script src="xx" async></script>

    是否阻塞parse: 不阻塞, 继续parse
    执行时机:js文件加载完成后,下一个时间点执行js(相当于setTimout(fn, 0))

  6. script 代码块, 前面没有css文件在加载

    1
    2
    3
    <script>
    code
    </script>

    是否阻塞parse: 不阻塞, 执行完代码继续parse
    执行时机:立即执行js,执行完继续parse

  7. script 代码块, 前面有css文件在加载

    1
    2
    3
    <script>
    code
    </script>

    是否阻塞parse: 阻塞, 同4,停止parse,但是会触发preLoad
    执行时机:待css文件加载完毕,解析完css后,立即执行js,执行完继续parse

  8. 动态插入的script标签

    1
    document.append(script)

    同5,动态标签不受defer,async,type=’module’影响,全部async

  9. script type=module + defer

    1
    <script type=module defer></script>

    同5,async优先级高于defer

  10. script async + defer

    1
    <script src='xxx' async defer></script>

    同5

  11. script 代码块 + defer

    1
    2
    3
    <script defer>
    code
    </script>

    script代码块不支持 defer,同6或7

  12. script 代码块 + async

    1
    2
    3
    <script async>
    code
    </script>

    script代码块不支持 async,同6或7

  13. script 代码块 + src

    1
    2
    3
    <script src='xxx'>
    code
    </script>

    忽略代码块中的code,优先src

正则表达式前端使用手册

发表于 2017-06-15 | louis | 分类于 正则 | | 阅读次数

关于

  • 我的博客:louis blog
  • SF专栏:路易斯前端深度课
  • 原文链接:正则表达式前端使用手册

导读

你有没有在搜索文本的时候绞尽脑汁, 试了一个又一个表达式, 还是不行.

你有没有在表单验证的时候, 只是做做样子(只要不为空就好), 然后烧香拜佛, 虔诚祈祷, 千万不要出错.

你有没有在使用sed 和 grep 命令的时候, 感觉莫名其妙, 明明应该支持的元字符, 却就是匹配不到.

甚至, 你压根没遇到过上述情况, 你只是一遍又一遍的调用 replace 而已 (把非搜索文本全部替换为空, 然后就只剩搜索文本了), 面对别人家的简洁高效的语句, 你只能在心中呐喊, replace 大法好.

为什么要学正则表达式. 有位网友这么说: 江湖传说里, 程序员的正则表达式和医生的处方, 道士的鬼符齐名, 曰: 普通人看不懂的三件神器. 这个传说至少向我们透露了两点信息: 一是正则表达式很牛, 能和医生的处方, 道士的鬼符齐名, 并被大家提起, 可见其江湖地位. 二是正则表达式很难, 这也从侧面说明了, 如果你可以熟练的掌握并应用它, 在装逼的路上, 你将如日中天 (别问我中天是谁……) !

阅读全文 »

如何用 Chrome DevTools 调试 iOS Safari

发表于 2017-06-08 | xietian | 分类于 browser | | 阅读次数

前言

    app内安卓调试直接用usb连接安卓手机后打开安卓开发者模式usb通过就可以。
一般情况下,如果前端工程师要调试 iOS 设备 Safari 浏览器上的表现,需要在 iOS 系统设置中为 Safari 开启 Web 检查器,并用 macOS 上的 Safari 开发者工具调试。但是早已习惯了强大的 Chrome DevTools 的我们,在使用 Safari 的开发者工具时,还是会有诸多不便。
因此 Google 利用 Apple 的 远程 Web 检查器服务 制作了可以将 Chrome DevTools 的操作转为 Apple 远程 Web 检查器服务调用的协议和工具:iOS WebKit Debug Proxy(又称 iwdp)。
下面就来简单介绍一下如何使用这个工具来让我们用 Chrome DevTools 调试 iOS Safari 页面。

环境依赖

  • macOS/Linux (Linux 需要查看 Usage)
  • Xcode
  • Chrome
  • ios-webkit-debug-proxy。

Bash

示例:

1
2
3
$ brew update
$ brew install libimobiledevice
$ brew install ios-webkit-debug-proxy

How to Start

iwdp 支持 iOS 模拟器,也支持真机设备,但使用模拟器时,必须先启动模拟器,再启动代理(iwdp)。
启动 iOS 模拟器,或者直接用物理设备(iPhone/iPad):
Bash

1
2
3
4
# Xcode changes these paths frequently, so doublecheck them
$ SDK_DIR="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs"
$ SIM_APP="/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator"
$ $SIM_APP -SimulateApplication $SDK_DIR/iPhoneSimulator8.4.sdk/Applications/MobileSafari.app/MobileSafari

修改 iOS Safari 设置,并打开一个 Safari 浏览器选项卡:

1
2
Settings > Safari > Advanced > Web Inspector = ON  
设置 > Safari > 高级 > Web 检查器 = 启用

开启代理
用 -f 可以选择指定的 DevTools UI 介面,这个介面可以是任意的,这里我们用 Chrome 自带的开发者工具:
Bash

1
2
3
4
$ ios_webkit_debug_proxy -f chrome-devtools://devtools/bundled/inspector.html
Listing devices on :9221
Connected :9222 to SIMULATOR (SIMULATOR)
Connected :9223 to Daft Punk (我的真机)

打开 http://localhost:9221/,如下:
IOS Devices:

1.localhost:9222 - SIMULATOR
1.localhost:9223 - Daft Punk

进入 SIMULATOR 的页面:

Inspectable pages for SIMULATOR:

1.xxxx
2.xxxx

注意:因为 Chrome 不允许打开 chrome-devtools:// 协议的链接,所以需要我们复制链接在地址栏手动打开.

错误处理

问题 1:

Bash

1
2
3
4
5
6
7
8
$ ios_webkit_debug_proxy
Listing devices on :9221
Could not connect to lockdownd. Exiting.: Permission deniedConnected :9222 to SIMULATOR (SIMULATOR)
Invalid message _rpc_reportConnectedDriverList: <dict>
<key>WIRDriverDictionaryKey</key>
<dict>
</dict>
</dict>

解决方式:
Bash

1
sudo chmod +x /var/db/lockdown

问题 2:
Bash

1
2
3
4
5
6
7
8
9
10
$ ios_webkit_debug_proxy
Listing devices on :9221
Connected :9222 to SIMULATOR (SIMULATOR)
Invalid message _rpc_reportConnectedDriverList: <dict>
<key>WIRDriverDictionaryKey</key>
<dict>
</dict>
</dict>
Disconnected :9222 from SIMULATOR (SIMULATOR)
send failed: Socket is not connected

解决方式,重装依赖工具:

Bash

1
2
$ brew uninstall --force ignore-dependencies -libimobiledevice ios-webkit-debug-proxy
$ brew install libimobiledevice ios-webkit-debug-proxy

问题3:

Bash

1
2
3
4
5
$ ios_webkit_debug_proxy
Listing devices on :9221
Connected :9222 to SIMULATOR (SIMULATOR)
send failed: Socket is not connected
recv failed: Operation timed out

解决方式,指定 UI 介面:

Bash

1
$ ios_webkit_debug_proxy -f chrome-devtools://devtools/bundled/inspector.html

问题4:

连接 iOS 10 设备可能会出以下报错,无法连接设备:

Bash

1
2
3
4
$ ios_webkit_debug_proxy -f chrome-devtools://devtools/bundled/inspector.html
Listing devices on :9221
Could not connect to lockdownd. Exiting.: Broken pipe
Unable to attach <id> inspector

更新最新的 libimobiledevice 即可:

1
2
Bash
$ brew upgrade libimobiledevice --HEAD

引用

  • google/ios-webkit-debug-proxy

  • Could not connect to lockdownd. Exiting Permission denied #160

  • send failed: Socket is not connected #19

  • could not connect to lockdownd. Exiting.: Permission denied #168

12
PAJK-FE

PAJK-FE

平安健康前端博客

17 日志
7 分类
8 标签
GitHub
© 2017 - 2018 PAJK-FE
由 Hexo 强力驱动
主题 - NexT.Mist