# 2020 / 3 / 15

内容出自掘友「前端技匠」 入口:https://juejin.im/post/5e6b05116fb9a07cb83e39c6

# 1 / 手写继承

  • 组合继承
function Person(){
    this.name = name
}
Parent.prototype.getName = function(){
    console.log(this.name)
}
function Child(name){
    Parent.apply(this, name)
}
Child.prototype = new Parent()
1
2
3
4
5
6
7
8
9
10
  • 寄生组合继承
function Parent(name){
    this.name = name
}
Parent.prototype.getName = function(){
    console.log(this.name)
}
function Child(name){
    Parent.call(this, name)
}
Child.prototype = Object.create(Parent.prototype, {
    constructor:{
        value: Child,
        enumerable: false,
        writable: true,
        configurable: true
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 2 / instanceof实现原理

原理:利用原型链进行查找,例如 A instanceof B,在A的原型链中层层查找是否有原型等于 B.prototype ,直到原型链顶端(Object.prototype.proto),找到则为true,否则为false

function myInstanceof(left, right){
    let R = right.prototype,
        L = left.__proto__
    
    while(true){
        if(L === null) return false
        if(L === R) return true

        L = L.__proto__
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 3 / Promise 限制并发数

利用任务队列这种思想,在每次要执行“受限”任务时,判断当前正在执行的任务数量是否超过给定的上限,如果未超过则立即执行这个“任务”,否则进入任务队列中等待执行

class LimitPromise{
    constructor(max){
        this.max = max  // 异步任务 '并发' 上限
        this.count = 0  // 当前正在执行的任务数量
        this.taskQueue = [] // 等待执行的任务队列
    }

    /*
        创建一个任务
        @params caller 实际执行的函数
        @params args 执行函数的参数
        @params resolve
        @params reject
        @return 返回一个任务函数
    */
    createTask(caller, args, resolve, reject){
        return () => {
            // 实际上是在这里执行了异步任务,并将异步任务的返回(resolve,reject)抛给上层
            caller(...args)
                .then(resolve)
                .catch(reject)
                .finally( () => {
                    // 任务队列的消费区,利用 promise 的 finally 方法,在异步任务结束后,取出下一个任务执行
                    this.count--
                    if(this.taskQueue.length){
                        let task = this.taskQueue.shift()
                        task()
                    }else{
                        // ...
                    }
                })
                this.count++
        }
    }


    /*
        调用器,将异步任务函数和它的参数传入
        @params caller 异步任务函数,他必须是async函数或者返回Promise的函数\
        @params args 异步任务函数的参数列表
        @return 返回一个新的Promise
    */
    call(caller, ...args){
        return new Promise((resolve, reject) => {
            const task = this.createTask(caller, args, resolve, rejecet)
            if(this.count >= this.max){
                this.taskQueue.push(task)
            }else{
                task()
            }
        })
    }
}
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

# 4 / 箭头函数跟普通函数的区别

  • 箭头函数相当于匿名函数,不能用作构造函数,不能使用new
  • 箭头函数没有自己的this,会捕获其所在上下文的this值来作为自己的this值
  • 箭头函数不能访问 arguments 对象,取而代之的是用rest参数(...rest)解决
  • 箭头函数通过 call() 或 apply() 调用一个函数时,只传入一个参数,对 this 没有影响(因为箭头函数中的 call,apply 方法指向的对象会被忽略)
  • 箭头函数不能使用 yield 命令,因此箭头函数不能用作 Generator 函数
  • 箭头函数没有原型属性

# 5 / vue 双向绑定原理

Vue主要通过以下4个步骤实现数据双向绑定

  • 实现一个监听器(Observer):对数据对象进行遍历,包括子属性对象的属性,利用Object.defineProperty()在属性上都加上getter和setter,这样之后,给对象的某个值进行操作,就会触发setter,那么就可以监听到数据的变化
  • 实现一个解析器(Compile):解析Vue模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点上都绑定上更新函数,添加监听数据的订阅者,一旦数据发生变化,就会收到通知,调用绑定好的更新函数进行数据更新
  • 实现一个订阅者(Watcher):订阅者是监听器和解析器之间通信的桥梁,主要任务是订阅 Observer(监听者)中的属性值变化的消息,当收到属性值变化的消息后,就会去通知解析器,解析器收到通知后触发更新函数
  • 实现一个消息订阅器(Dep):订阅器采用发布订阅的设计模式,用来收集订阅者,对监听器(Observer)和订阅者(Watcher)进行统一管理

# 6 / https 实现原理(越详细越好)

Https(超文本安全传输协议) 是以安全为目标的Http通道,Https的安全基础是基于 SSL/TLS协议(安全传输层协议),是介于Tcp和Http之间的一层安全协议。

TLS/SSL协议的功能实现主要依赖于三类算法:非对称加密,对称加密,散列函数Hash

客户端使用非对称加密与服务器进行通信,实现身份验证并协商对称加密所使用的密钥,然后采用对称加密算法通过协商好的密钥对信息以及信息摘要进行加密通信,不同节点之间采用的对称密钥不同,从而可以保证信息只能通信双方获取

身份验证通过CA和证书(扩展详述)

# 7 / 浏览器渲染页面过程

  • 输入URL
  • 浏览器先查看 '浏览器缓存','系统缓存','路由器缓存',如果存在缓存可用,则直接显示
  • 若无,浏览器根据DNS域名,解析域名获取Ip地址
    • DNS查询步骤:域名服务器递归查询:根域名服务器 → 顶级域名服务器 → 权威DNS域名服务器 → 相应的主机Ip地址
  • 根据Ip地址和默认端口(Http为80,Https为443),与服务器建立Tcp连接(三次握手)
  • 发送Http请求,服务器处理请求,返回响应结果
  • 释放Tcp连接(四次挥手)
  • 浏览器将得到的 Html 文本渲染出来
    • 通过建立Html代码构建Dom树,然后合并CSS树构建Render树,最后绘制Render树并渲染在页面上

# 8 / 前端模块化的理解

模块化的意义首先在于分治的思想,对功能进行分治,有利于维护,其次就是复用,有利于开发

为什么需要模块化

  • web sites 慢慢变成了 web app
  • 随着项目的扩大,代码量越来越大,代码越来越复杂
  • 对代码,文件的高度解耦的要求
  • 优化上,希望减少http请求

借鉴 https://segmentfault.com/a/1190000015437724?utm_source=tag-newest

# 9 / 隐式转换

转换为字符串 转换为数字 转换为布尔值 转换为对象
undefined 'undefined' NaN false throw TypeError
null 'null' 0 false throw TypeError
true 'true' 1 new Boolean('true')
false 'false' 0 new Boolean('false')
'' 0 false new String('')
'1.2' 1.2 true new String('1.2')
'1.2a' NaN true new String('1.2a')
'aaa' NaN true new String('aaa')
0 '0' false new Number(0)
-0 '0' false new Number(-0)
1 '1' true new Number(1)
NaN 'NaN' false new Number(NaN)
Infinity 'Infinity' true new Number(Infinity)
-Infinity '-Infinity' true new Number(-Infinity)
[] '' 0 true
[9] '9' 9 true
['a','b'] 'a, b' NaN true

注意:

  • 数组类型的类型转换,数组转换为字符串,实际上是调用不传参数的join()方法
  • 转为布尔类型为false的有:undefined,null,0,-0,NaN,'',
  • 字符串类型转为数值类型:
    • 若字符串中出现任意非数字,非空格的字符,均转为NaN
    • 若数字中间存在空格,转为NaN
    • 若希望尽可能的转换字符串中出现的数字,参考parseInt,parseFloat方法
  • new Numbr() 和 Number() 是不同的
    • new Number() 是创建一个Number对象
    • Number()将传入的参数转换为数值字面量

关于运算:

  • 加法运算:
    • 当运算符其中一方为字符串时,那么另一方也转换为字符串
    • 当一侧为Number类型,另一侧为原始类型,则将原始类型转换为Number
    • 当一侧为Number类型,另一侧为引用类型,则将引用类型和Number类型转换为字符串后拼接
  • 除加法外,只要其中一方为数字,那么另一方就会转换为数字
  • 比较运算符(==)
    • NaN:和其它类型比较均返回false(包括自己)
    • Boolean:和其他类型比较,Boolean首先被转化为Number(true - 1;false - 0)
    • String 和 Number:String先转化为Number类型,再进行比较
    • Null 和 undefined:null == undefined 为 true,除此之外,null,undefined与其他任何值比较均为false
    • 原始类型和引用类型:引用类型转换为原始类型

# 10 / 关于Webpack的问题(自行取经)

  • webpack 如何实现动态加载
  • webpack 的 require 是如何查找依赖的
  • webpack 优化

# 11 / 变量提升 let const var 区别

  • var 声明变量
    • var 没有块级概念,可以跨块访问
    • var 存在变量提升,我们能在声明之前使用
  • let 声明块级变量
    • let存在块级概念,只能在块级作用域中使用,不能跨块访问
    • let 存在暂时性死区,必须在变量声明之后才可以使用
  • const 声明块级常量,一旦赋值便不能修改(内存地址不能修改)
    • const存在块级概念,只能在块级作用域中使用,不能跨块访问
    • const 存在暂时性死区,必须在声明之后才可以使用
  • let,const不允许在相同作用域内重复声明同一个变量
  • var在全局作用域下声明的变量会导致变量被挂载到window上,而let 、const不会

# 12 / script 标签中 async 跟 defer 的区别

区别在于异步脚本的执行顺序

  • defer
    • 这个属性的用途是表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在<script>元素中设置defer属性,相当于告诉浏览器立即下载,但延迟执行。
    • HTML5规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于DOMContentLoaded事件执行。
  • async
    • 同样与defer类似,async只适用于外部脚本文件,并告诉浏览器立即下载文件。但与defer不同的是,标记为async的脚本并不保证按照它们的先后顺序执行。
    • 第二个脚本文件可能会在第一个脚本文件之前执行。因此确保两者之间互不依赖非常重要。指定async属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容

借鉴 https://segmentfault.com/a/1190000006778717