前端基础知识回顾

前端基础知识回顾

一段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;
    };