# 2020 / 3 / 6

内容出自掘友「杨珏成」 入口:https://juejin.im/post/5d70ff205188253e4b2f07bd#heading-17

# 阿里实习

# 1 / 用 Javascript 描述一棵树

// 二叉树
var tree = {
    value:1,
    left:{
        value:2,
        left:null,
        right:null
    },
    right:{
        value:3,
        left:null,
        right:null
    }
}

// 多叉树
var tree = {
    value:1,
    children:[
        {
           value:2,
           children:[
               {
                   value:5
               },
               {
                   value:6
               }
           ]
        },
        {
           value:3,
           children:[
               {
                   value:7
               },
               {
                   value:8
               }
           ]
        },
        {
           value:4
        }
    ]
}
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

# 2 / 非递归遍历树

function traverseTree(node){
  if (!node) {
    return
  }
  var stack = [] // 存放当前遍历到的节点
  stack.push(node)  // 将根节点存入
  var tmpNode
  while (stack.length > 0) {
    tmpNode = stack.pop()  // 当前遍历到的节点
    
    // todo → 可以操作当前节点

    // 遍历下一个节点
    if (tmpNode.children && tmpNode.children.length > 0) {
      var i = tmpNode.children.length - 1
      for (i = tmpNode.children.length - 1; i >= 0; i--) {
        stack.push(tmpNode.children[i])
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 3 / 详述 Javascript - new 操作

理解 new 做了什么事

  • 创建一个全新的对象(空对象)
  • 对这个全新的对象进行原型连接(proto)
  • 将 this 指向这个新创建的对象
  • 执行这个构造函数中的所有方法
  • 如果函数没有返回对象类型 Object,那么 new 表达式中的函数调用将返回该对象引用

手写一个 new

function myNew(){
    const obj = function(){} // 创建一个空对象
    const Constructor = [].shift.call(arguments) // 删除arguments第一个元素并获取这个元素

    obj.__proto__ = Constructor.prototype // 对空对象进行原型链接

    const returnObj = Constructor.call(obj, arguments) // 执行构造函数,同时this指向这个空对象,并获取返回值以判断构造函数有没有返回一个对象

    return typeof returnObj === 'object' ? returnObj : obj
}

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

myNew(Person, 'chicago')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 4 / 函数调用模式this指向 & 方法调用模式this指向

this 指向有四种绑定

  • 默认绑定 (this指向全局)
  • 隐式绑定(xxx.fun())
  • 显示绑定(call() / apply() / bind())
  • new绑定

函数调用模式:如果一个函数不是一个对象的属性,就是被当成一个函数进行调用。此时 this 指向 window

function fn(){
    console.log(this)
}
fn() // window
1
2
3
4

方法调用模式:当一个函数被保存为对象的一个属性时,称之为一个方法。当一个方法被调用时,this 指向 调用这个方法的对象

var obj = {
    sayHi:function(){
        console.log(this)
    }
}
obj.sayHi()  // obj
1
2
3
4
5
6

# 5 / 什么是 Js 闭包

闭包就是一个函数A内部存在另一个函数B,函数B可以访问到函数A中的变量和方法,此时函数B就是一个闭包

function A(){
    let a = 1
    window.B = function(){
        console.log(a)
    }
}
A() // 定义函数内部变量a,赋值window.B
B() // 1  访问到函数A内部变量
1
2
3
4
5
6
7
8

闭包存在意义:让我们可以间接的访问到函数内部的变量和方法,函数内部变量的引用也会在内部函数中,即使外部函数已经返回,这个变量也不会销毁,但过多的闭包可能造成内存泄漏

闭包的三个特性

  • 闭包可以访问当前函数以外的变量
  • 即使外部函数已经返回,闭包仍能访问外部函数定义的变量(保持对外部函数变量的引用)
  • 闭包可以更新外部变量的值

# 6 / 如何跨域访问?

  • Cors :服务端设置请求头(Access-Control-Allow-Origin)
  • jsonP :利用img、srcipt,link标签的src或href属性(到底使用那个标签无所谓)来实现(只能够解决GET请求)
  • window.name :利用浏览器窗口内,载入所有的域名都是共享一个window.name
  • document.domain / window.postMessage()

# 7 / vue的父子组件之间如何通信

vue 父子组件通信有以下方式:

  • prop / $emit
  • $on / $emit (eventBus)
  • $parents / $children
  • ref
  • provide / inject
  • .sync

# 8 / 用css写无限循环动画

animation属性

  • animation-name :绑定的keyframe名称
  • animation-duration :规定完成动画所花费的事件 (s/ms)
  • animation-timing-function :规定动画的速度曲线
  • animation-delay :规定动画开始之前的延迟
  • animation-iteration-count :规定动画应该播放的次数
  • animation-direction :规定是否应该轮流反向播放动画
.xxx{
    animation:Animation 5s infinite;
}
@keyframes Animation
{
    // 动画
}
1
2
3
4
5
6
7

# 9 / 如何响应式布局

所谓响应式布局,就是一个网站能够兼容多个终端,而不是为每个终端做一个特定的版本,他的好处我们不用多套屏幕模板而只需要写一个就可以实现

原理:为一个元素设置多个类,然后通过媒体查询根据当前屏幕分辨率来选择适合的类进行渲染

# 10 / 如何清除float

  • 父级元素添加 overflow 属性,或者设置高度(触发父元素BFC)
<div class='Parent' style='oveflow:auto'>
    <div class='Float'></div>
</div>
1
2
3
  • 父级元素使用伪类
<div class='Parent'>
    <div class='Float'></div>
</div>
1
2
3
.Parent:after{
    content:'',
    display:block;
    height:0;
    visibility:hidden;
    clear:both;
}
1
2
3
4
5
6
7
  • 添加额外标签
<div class='Parent'>
    <div style='clear:both'></div>
    <div class='Float'></div>
</div>
1
2
3
4

# 11 / 手写jsonp

/**
 *  jsonp 返回 Promise 对象
 *  参数url,data:json对象, callback函数
*/
function jsonp(url, data = {}, callback = 'callback'){
    // 处理json对象,拼接url
    data.callback = callback
    let params = []
    for(let key in data){
        params.push(key + '=' + data[key])
    }
    
    // 创建script元素
    let script = document.crateElement('script')
    script.src = params.length > 0 ? url + '?' + params.join('&') : url
    document.body.appendChild(script)

    // 返回Promise
    return new Promise((resolve, reject) => {
        window[callback] = data => {
            try{
                resolve(data)
            }catch(e){
                reject(e)
            }finally{
                // 移除script元素
                script.parentNod.removeChild(script)
            }
        }
    })
}

jsonp('http://xxx/xxx/xxx', {
    username:'xxx'
}, 'jsoncallback').then(data => {
    console.log(data)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 12 / 为什么禁止跨域

跨域的访问会带来许多安全性的问题,比如,cookie 一般用于状态控制,常用于存储登录的信息,如果允许跨域访问,那么别的网站只需要一段脚本就可以获取你的 cookie,从而冒充你的身份去登录网站,造成非常大的安全问题,因此,现在浏览器均推行同源策略(同域名、同协议、同端口)

# 13 / tcp 三次握手 & 四次挥手

三次握手

  • 第一次握手:建立连接时,客户端发送SYN包(SYN = j)给服务器,并进入SYN_SENT状态,等待服务器确认 【SYN - 同步序列编号】
  • 第二次握手:服务器收到SYN包,必须确认客户的SYN(SYN = j + 1),同时自己也发送一个SYN包(SYN = k),即SYN + ACK包,此时服务器进入SYN_RECV状态
  • 第三次握手:客户端收到服务器的SYN + ACK包,向服务器发送确认包ACK(ACK = k + 1),此包发送完毕,客户端和服务器进入ESTABLISHED状态(TCP连接成功),完成三次握手

四次挥手

  • 客户端进程发出连接释放报文,并且停止发送数据。此时客户端进入FIN-WAIT-1状态(终止等待1)
  • 服务器收到连接释放报文,发出确认报文。此时服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然还要接受。这个状态还需要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间
  • 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2状态(终止等待2),等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)
  • 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,此时服务器就进入LAST-ACK状态(最后确认),等待客户端确认
  • 客户端收到服务器的连接释放报文后,必须发出确认,此时客户端进入TIME-WAIT状态(时间等待),注意此时TCP连接还没有释放,必须经过最长报文段寿命的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态
  • 服务器只要收到客户端发出的确认,立即进入CLOSED状态,同样,撤销TCB后,就结束了这次TCP连接(服务器结束TCP连接的时间要比客户端早一些 - 因为客户端要经过最长报文段寿命的时间,而服务器立即就会进入CLOSE状态)

# 14 / setTimeout为何能在单线程的js里异步执行?

事件循环中,事件执行顺序是 宏任务 → 微任务 → 下一个宏任务,而 setTimeout 属于宏任务,因此会被挂起到下一个宏任务中执行

# 15 / 进程和线程的区别 ?

  • 进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程
  • 线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可以共享数据

区别:

  • 调度:进程是拥有资源的基本单位,线程是独立调度的基本单元。在同一进程中,线程的切换不会引起进程的切换,而不同进程中进行的线程切换,则会引起进程的切换
  • 拥有资源:进程是拥有资源的基本单位,而线程不会拥有资源,但线程可以共享其隶属于进程的资源
  • 并发性:进程可以并发执行,而且同一进程的多个线程也可以并发执行,大大提高系统吞吐量
  • 系统开销:线程之间的同步与通信比较容易实现
  • 通信方面:进程间通信需要借助操作系统,而线程间可以直接读写进程数据段来进行通信
  • 地址空间和其他资源:进程的地址空间之间相互独立,而同一进程的各个线程间共享进程的资源,某进程内的线程对于其他进程不可见

# 16 / bind和call有什么区别?

call() 、apply()都属于立即执行函数,区别在于接收形式不同,前者是依次传入参数,而后者可以是数组

bind()非立即执行函数,它是一个新的包装类型,并且返回。

手写一个bind

Function.prototype.myBind = function(Arugment){
    // 判断this指向的是否为function
    if(typeof this !== 'function'){
        throw new TypeError('this should be a function')
    }

    const args = [].slice.call(arguments, 1) // 截取除第一个参数之外的参数
    const self = this // this指向调用myBind的函数

    const savePrototype = function(){} // 用于保存原函数的原型对象

    const bind = function(){
        return self.apply(this instanceof savePrototype ? this : Argument, args.concat([].slice.call(arguments)))
    }

    if(this.prototype){
        savePrototype.prototype = this.prototype
    }

    bind.prototype = new savePrototype()
    
    return bind
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 17 / 手写快排

function quickSort(array){
    if(array.length <= 1){
        return array
    }
    var middleIndex = Math.floor(array.length / 2)
    var middleNum = array.splice(middleIndex, 1)[0]
    console.log(middleNum)
    var left = []
    var right = []
    for(var i = 0; i < array.length; i++){
        if(array[i] < middleNum){
            left.push(array[i])
        }else{
            right.push(array[i])
        }
    }

    return quickSort(left).concat([middleNum], quickSort(right))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 18 / 讲一下http状态码 & 303、304代表什么?

  • 2xx:代表请求已成功被服务器接收,理解
    • 200:表示请求已成功,请求所希望的响应头或数据体将随此响应返回
    • 201:表示请求成功并且服务器创建了新的资源,且其URL已经随Location头信息返回,假如需要的资源无法及时建立,应当返回 202 Accepted
    • 202:服务器已接受请求,但尚未处理
  • 3xx:代表需要客户端采取进一步的操作才能完成请求,这些状态码用来重定向,后续的请求地址(重定向目标)在本次响应的Location域中指明
    • 301:被请求的资源已永久移动到新的位置。(永久重定向)
    • 302:请求的资源临时从不同的URL响应请求,但请求者应继续使用原有位置来进行以后的请求
    • 304:自上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。
  • 4xx:表示客户端请求错误,妨碍了服务器的处理
    • 401:请求要求身份验证,对于需要登录的网页,服务器可能返回此响应
    • 403:服务器已经理解请求,但是拒绝执行它
    • 404:请求失败,请求所希望得到的资源未被在服务器上发现
  • 5xx:代表了服务器在处理请求的过程中有错误或者异常状态发生,也有可能是服务器意识到以当前的软硬件资源无法完成对请求的处理
    • 500: 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器的程序码出错时出现
    • 503:由于临时的服务器维护或者过载,服务器当前无法处理请求(通常,这个是暂时状态,一段时间会恢复)

303:对应当前请求的响应可以在另一个 URL 上被找到,而且客户端应当采用 GET 的方式访问那个资源

304: 如果客户端发送了一个带条件的GET请求且该请求已被允许,而文档的内容自上次访问以来并没有被修改,则服务器返回这个状态码。 304响应禁止包含消息体,因此始终以消息头后的第一个空行结尾

区别:

  • cookie数据保存在用户浏览器上,而session数据保存在服务器上
  • cookie不安全,cookie以明文方式储存在客户端中,别人可以通过分析存放在本地的cookie并进行cookie欺骗,而session存放在服务器,安全性较好,考虑到安全应当使用session
  • session会在一定时间内保存在服务器上,比较占用服务器性能,若考虑服务器性能方面,应当使用cookie
  • 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie

个人认为登录信息等重要信息存放为session,其他信息如果需要保留,可以存放在cookie

# 20 / 同源的定义

同源指同协议,同域名,同端口