前端基础知识回顾

一段html代码

1
2
3
4
5
6
7
8
9
10
11
12
<body>
<a href='https://www.baidu.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
    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:事件冒泡阶段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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)
  4. 点击穿透 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()
    }
  3. 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())
  4. 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++
    }
  5. 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())
  6. 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
    }
  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
    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')
    }
  8. 如何实现一个 promise.all (待完善)

  9. generator yield next (待完善)

  10. async await (待完善)

  11. 实现 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;
    };