面试笔记整理

55 阅读25分钟

JS

1.数据类型

基本类型、引用类型

基本类型:

  • Number
  • String
  • Boolean
  • Undefined:声明但未赋值
  • Null:空对象指针
  • Symbol:值都是唯一的

引用类型:

  • 对象
  • 数组:每一个元素都可以是不同的数据类型
  • 函数:函数声明,函数表达式,箭头函数

存储上的区别:基本数据类型存储在栈中;引用数据类型的对象存储在堆中,每个堆有一个引用地址,引用地址存储在栈中。复杂类型的赋值其实是将对象的内存地址赋给另一个变量(两个变量指向堆中的同一个对象)

手写判断数据类型

function getType(obj){
  let type  = typeof obj;
  if (type !== "object") {    // 先进行typeof判断,如果是基础数据类型,直接返回
    return type;
  }
  // 对于typeof返回结果是object的,再进行如下的判断,返回结果
  return Object.prototype.toString.call(obj). slice()
}

2.数组的常用方法

增删改查

push() 返回新数组长度

unshift() 返回新数组长度

splice(x,0,y) 返回空数组

concat 返回新建的数组,不影响原数组

pop() 返回被删除元素

shift() 返回被删除元素

splice(x,m) 从第x个元素开始删除m个元素,返回包含删除元素的数组

slice(x) 从第x个元素开始的新数组,不影响原数组

splice()

indexOf() 返回元素索引值,未查询到则返回-1

includes() 返回布尔值

find() 返回第一个匹配的元素

排序

reverse() 反转,直接影响原数组

sort(fn) 排序,fn为排序函数,fn(a,b) 返回值为-1则a在左边,返回值为1则b在左边

转换

join(",") 将数组转换为字符串,元素之间补“,”

迭代

some() 有一个满足则返回true

every() 全部满足则返回true

forEach() 对每一个进行测试

filter() 将满足的元素作为新数组返回

map() 将所有测试结果作为新数组返回

深拷贝

let newArr = JSON.parse(JSON.stringify(arr));

3. 字符串常用方法

增删改查

concat()

slice() 从第x个字符起,到第m个字符

substring() 从第x个字符起,到第m个字符

substr() 从第x个字符起的m个字符

trim() 删除空格 trimLeft() trimRight()

repeat(n) 重复n次

padEnd(n) 补字符到n位

toLowerCase() toUpperCase() 大小写转换

charAt() 返回指定索引的字符

indexOf()

includes() startWith() 返回布尔值

转换

split() 以’‘为分割,转换为数组

正则相关

match() replace() 默认只对第一次匹配生效,返回匹配到的数组/替换,返回新字符串

search() 不可设置全局模式,返回第一次匹配的索引

4. 类型转换机制

显式转换

Number() parseInt()

  • undefined -> NaN ; null -> 0 ;

String()

Boolean()

  • 空字符串、0/NaN、null/undefined -> false

隐式转换

+常自动转换为String

5.==和===的区别

==

等于操作符,先类型转换再比较

布尔、字符串转换为数值,null和undefined相等,NaN不与任何相等

两个对象比较看指针是否指向相同

===

null与undefined严格与自身相等

在需要比较null时常用==来判断

6.深拷贝与浅拷贝

浅拷贝:创建新的数据,有着原始数据属性值的一份准确拷贝(对于引用类型则拷贝数据地址)

简单实现浅拷贝:

function shallowClone(obj) {
    const newObj = {};
    for(let prop in obj) {
        if(obj.hasOwnProperty(prop)){
            newObj[prop] = obj[prop];
        }
    }
    return newObj;
}

深拷贝:开辟一个新的栈,两个对象属性完全相同,但是对应两个不同的地址

浅拷贝修改一个对象的属性另一个对象的属性也会被修改,而深拷贝不会

手写深拷贝:

function deepClone(obj) {
    //如果obj不是对象或为空
    if (typeof obj !== 'object' || obj == null) {
        return obj
    }
    //初始化
    let a
    if (obj instanceof Array) {
        a = []
    } else {
        a = {}
    }
    
    for (let key in obj) {
        //判断不是原型而是数据
        if (obj.hasOwnProperty(key)) {
            a[key] = deepClone(obj[key])
        }
    }
    return a
}

7.闭包

一个函数和对其周围状态的引用捆绑在一起就是闭包

1.函数嵌套 2.内部函数使用外部函数的变量 3.外部函数被调用

闭包的作用:1.延长局部变量的生命周期 2.让函数外部可以访问函数内部的数据 3.创建私有变量

缺点:1.局部变量占用内存时间变长 2.容易造成内存泄漏

8.作用域

变量和函数生效的区域

  • 全局作用域:不在函数或大括号里声明的变量,都是在全局作用域下,任何地方都可以访问
  • 函数作用域:在函数内声明的变量,仅在函数内部可以访问
  • 块级作用域:ES6里在大括号中使用let和const声明的变量,在大括号外不能访问

语法作用域

变量在创建时就确定好了,而非执行时

var a = 2;
function foo(){
    console.log(a)
}
function bar(){
    var a = 3;
    foo();
}
bar()  //2

作用域链:在JS中使用一个变量时,首先在当前作用域下寻找,若没有找到则在上一级作用域中寻找,直到找到或找到全局作用域中

9.原型

每个函数都有一个原型对象,默认为一个空的Object实例对象

原型对象有一个constructor属性,指向函数对象

每个函数都有显式原型(prototype)和隐式原型(proto)

实例的隐式原型指向其构造函数的显式原型

原型链

原型对象相当于一个公共区域,同一个类的所有实例都可以访问这个原型对象

因此常将实例具有的公有内容统一设置到原型中

除Object外,所有对象的原型对象都是一个 Object实例对象,因此所有原型对象的隐式原型指向Object对象的显式原型,而Object原型对象的隐式原型为null

所有函数都是由Function函数所构造的,因此所有函数(包括Function本身)都是 Function对象的实例,它们的隐式原型指向Function的显式原型

1.读取对象的属性值时,会沿着原型链查找

2.设置对象的属性值时,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并且设置其值

3.方法一般定义在原型中,属性一般通过构造函数定义在对象本身

10.typeof & instanceof

typeof返回一个字符串,表示未经计算的操作数的类型

null会返回object

A instanceof B 判断A是否为B的实例,如果B的显式原型对象在A对象的原型链上,就说明A是B的实例,则返回true,否则返回false

Object instanceof Function //True
Object instanceof Object  //True
Fuction instanceof Function  //True
Function instanceof Object  //True

11.this

this关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象

this在函数执行过程中,this一旦被确定了,就不可以再更改

默认绑定

在非严格模式下,谁调用函数,this就是谁

隐式绑定

函数作为对象的方法时,this是调用时的上一级对象

var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();

this永远指向的是最后调用它的对象,虽然fn是对象b的方法,但是fn赋值给j时候并没有执行,所以最终指向window

new绑定

通过构建函数new关键字生成一个实例对象,此时this指向这个实例对象

显式修改

apply()、call()、bind()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this指的就是这第一个参数

new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级

12.apply()&call()&bind()

作用:改变函数执行的上下文,即改变函数的this的指向

apply()

  • 接收两个参数,第一个是函数this的指向,第二个是函数接收的参数,以数组形式传入
  • 改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次

call()

  • call方法的第一个参数也是this的指向,后面传入的是一个参数列表
  • 跟apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次

bind()

  • bind方法和call很相似,第一参数也是this的指向
  • 后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)
  • 改变this指向后不会立即执行,而是返回一个永久改变this指向的函数

13.执行上下文

变量提升与函数提升

1.变量提升:

  • 使用var关键字声明的变量,会在所有代码执行前被声明(但不会赋值)
  • 如果不使用var关键字,则变量不会被声明提前

2.函数提升:

  • 使用函数声明function a(){} 创建的函数,可以在创建前使用(在所有代码创建前被创建)
  • 使用函数表达式var a = function(){} 创建的函数会被变量提升而非函数提升,因此不能在声明前使用

先执行变量提升,再执行函数提升

执行上下文

  • 代码可分为全局代码和局部代码(即函数代码)

  • 全局执行上下文

    • 在执行全局代码前将window确定为全局执行上下文

    • 对全局数据进行预处理

      • var 定义的全局变量 ==>undefined,添加为window的属性
      • function定义的全局函数 ==>赋值(fun),添加为window的方法
      • this ==>赋值(window)
    • 开始执行全局代码

  • 函数执行上下文

    • 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象

    • 对局部数据进行预处理

      • 形参变量*>赋值(实参)*>添加为执行上下文的属性
      • arguments==>赋值 (实参列表),添加为执行上下文的属性
      • var 定义的局部变量==>undefined,添加为执行上下文的属性
      • function声明的函数==>赋值(fun),添加为执行上下文的方法
      • this ==>赋值(调用函数的对象)
    • 开始执行函数体代码

14.JS如何实现继承

原型链继承

  1. 创建父类型的对象赋值给子类型的原型
  2. 将子类型原型的构造属性设置为子类型
  3. 给子类型原型添加方法
  4. 创建子类型的对象:可以调用父类型的方法
  • 子类型的原型为父类型的一个实例对象
  //父类型
  function Supper(){  //Step1
    this.supProp = 'Supper property'
  }
  Supper.prototype.showSupperProp = function(){  //Step2
    console.log(this.supProp)
  }
  //子类型
  function Sub(){  //Step3
    this.subProp = 'Sub property'
  }
  Sub.prototype = new Supper()  //Step4子类型的原型为父类型的一个实例对象
  Sub.prototype.constructor = Sub  //Step5,若没有这一步,对象sub的构造函数为Supper()
  Sub.prototype.showSubProp = function(){  //Step6
    console.log(this.subProp)  
  }
  
  var sub = new Sub()  //Step7
  sub.showSupperProp()  //Supper property
  console.log(sub.constructor)  //Sub() 

借用构造函数继承

在子类型构造函数中调用父类型构造函数

  • 在子类型构造函数中通过call()调用父类型构造函数
  • 用于继承父类型的属性
  function Person(name,age){  
    this.name = name
    this .age = age
  }
  function Student(name,age,grade){
    Person.call(this,name,age)//相当于this.Person(name,age)
    this.grade = grade
  }
  var s = new Student('Tom',20,9)
  console.log(s.name,s.age,s.grade)  //Tom 20 9

组合继承

  1. 利用原型链实现对父类型对象的方法继承
  2. 利用call()借用父类型构造函数初始化相同属性

缺陷:父类型的实例需要构造两次,造成多构造一次的性能开销

因此,采用的是寄生组合式继承,ES6中extends关键字也是采用的寄生组合式继承

使用Object.create 就可以减少组合继承中多进行一次构造的过程

function clone (parent, child) {
    // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;
}
​
function Parent6() {
    this.name = 'parent6';
    this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
    return this.name;
}
function Child6() {
    Parent6.call(this);
    this.friends = 'child5';
}
​
clone(Parent6, Child6);
​
Child6.prototype.getFriends = function () {
    return this.friends;
}
​
let person6 = new Child6();
console.log(person6); //{friends:"child5",name:"child5",play:[1,2,3],__proto__:Parent6}
console.log(person6.getName()); // parent6
console.log(person6.getFriends()); // child5

15.正则表达式

正则表达式是一种用来匹配字符串的工具

两种创建方式:1. new RegExp() 2.字面量创建const reg = /a|b/i

相关方法: split() search() match() replace() reg.test(str)返回布尔值

正则表达式的语法

var reg = /a{n}/;  //连续n个a
reg = /(ab){3} /;  //ababab
reg = / ab{3}c/;  //abbbc
reg = /ab{1,3}c/;// abc|abbc|abbbc   {m,n} m-n次 {m,}至少m次
reg = /ab+c/;  //b+相当于b{1,}
reg = /ab*c/;  //b* 有没有b 都行,相当于b{0,}
reg = /ab?c/;  //b?相当于b{0,1}
//检查一个字符串是否以a开头
reg = /^a/;  //^表示开头,匹配开头a;$表示结尾
reg = /a$/;  // 表示以a结尾   /^a$/只能是一个a
reg = /^a|a$/;  //表示以a开头或结尾 
\w:任意字母数字和下划线  [A-z0-9_]
\W:除\w
\d:任意数字
\D:除了\d
\s:空格
\S:除了\s
\b:单词边界,如\b child\b检测是否含有独立单词child
\B:除了\b
/^\s*|\s*&/g   匹配开头或结尾的空格

贪婪模式:尽可能多,懒惰模式:尽可能少

16.箭头函数

  • this是静态的,在函数定义时就决定了,无法改变
  • 不能作为构造函数实例化对象
  • 不能使用arguments变量
  • 适合与this无关的回调,定时器,数组的方法回调

17.Set&map

Set集合

一堆无序的、不重复的元素组成的组合

let set = new Set(arr)
//数组去重
let arr = [1,2,3,3,2,5,6]
let result = [...new Set(arr)] //创建一个内容为arr的集合,然后扩展并转换为数组
...是将数组转化为以逗号分隔的参数序列

Map

键值对的有序列表,键值都可以是任意数据类型

WeakSet

没有size属性,没有遍历操作的API

成员只能是引用类型

WeakMap

没有clear()方法,没有遍历操作的API

只接受对象(null除外)作为键名

18.ES6模块化

模块功能主要由两个命令构成:

  • export:用于规定模块的对外接口

    一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量

    也可以通过as关键字对变量重新命名

  • import:用于输入其他模块提供的功能

    使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块

    同样可以使用as重命名

    使用*表示导入模块中的全部内容

19.Promise

Promise是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大

传统的回调函数如果多层嵌套,就会出现回调地狱,使代码的可读性大大降低

而promise可以通过链式操作避免回调地狱,代码的可读性明显增强

  • promise有3种状态:进行中,成功,失败
  • 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
  • 一旦状态改变(从pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果

通过Promise构造函数创建promise对象

Promise((resolve,reject)=>{})需要传递一个函数作为参数,而函数中有两个参数resolve,reject,它们也是函数

有异步操作时使用promise对这个异步操作进行封装

new Promise时会立刻执行传入的函数

promise有then(),catch(),finally()函数,当异步操作成功时执行then,异步操作失败时执行catch,

then(()=>{},()=>{})可以直接传入两个函数,分别对应成功时和失败时的回调函数

链式调用

return new Promise((resolve)=>{resolve(res)}) 简写return Promise.resolve(res) 简写为return res

return new Promise((reject)=>{reject(err)}) 简写return Promise.reject(err) 简写为throw err

all()

Promise.all()方法用于将多个 Promise实例,包装成一个新的 Promise实例

const p = Promise.all([p1, p2, p3]);

p1,p2,p3都fulfilled, 则p会fulfilled,p1p2p3的返回值组成一个数组传递给p的回调函数

只要有一个rejected,p就会rejected,率先rejected的 Promise 实例的返回值则传递给p的回调函数

手写实现

function promiseAll(promises) {
    let len = promises.length
    let index = 0
    let data = []
    return new Promise((resolve, reject) => {
        for (let i in promises) {
            promises[i].then(res => {
                index++
                data[i] = res
                if (index == len) {
                    resolve(data)
                }
            }).catch(err => {
                reject(err)
            })
        }
    })
}

race()

Promise.race()方法也是用于将多个 Promise实例,包装成一个新的 Promise实例

const p = Promise.race([p1, p2, p3]);

只要有一个实例率先改变状态,p的状态就跟着改变

率先改变的 Promise 实例的返回值则传递给p的回调函数

手写实现

function PromiseRace(promises) {
    return new Promise((resolve, reject) => {
        for (let i in promises) {
            promises[i].then(res => {
                resolve(res)
            }).catch(err => {
                reject(err)
            })
        }
    })
}

20.new

  1. 创建一个新对象
  2. 将新对象的原型绑定到构造函数的原型链上
  3. 将this指向新的对象
  4. 返回新对象

21.防抖节流

  • 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
  • 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
//防抖
function debounce(func, wait) {
    let timeout;
​
    return function () {
        let context = this; // 保存this指向
        let args = arguments; // 拿到event对象
​
        clearTimeout(timeout)
        timeout = setTimeout(function(){
            func.apply(context, args)
        }, wait);
    }
}
​
//节流
function throttled(fn, delay) {
    let timer = null
    let starttime = Date.now()
    return function () {
        let curTime = Date.now() // 当前时间
        let remaining = delay - (curTime - starttime)  // 从上一次到现在,还剩下多少多余时间
        let context = this
        let args = arguments
        clearTimeout(timer)
        if (remaining <= 0) {
            fn.apply(context, args)
            starttime = Date.now()
        } else {
            timer = setTimeout(fn, remaining);
        }
    }
}

HTTP

1.HTTP

HTTP(HyperText Transfer Protocol),即超文本运输协议,是实现网络通信的一种规范

传输的数据并不是计算机底层中的二进制包,而是完整的、有意义的数据,如HTML 文件, 图片文件, 查询结果等超文本,能够被上层应用识别

在实际应用中,HTTP常被用于在Web浏览器和网站服务器之间传递信息,以明文方式发送内容,不提供任何方式的数据加密

特点如下:

  • 支持客户/服务器模式
  • 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快
  • 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记
  • 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间
  • 无状态:HTTP协议无法根据之前的状态进行本次的请求处理

2.HTTPS

HTTP传递信息是以明文的形式发送内容,这并不安全。而HTTPS出现正是为了解决HTTP不安全的特性

为了保证这些隐私数据能加密传输,让HTTP运行安全的SSL/TLS协议上,即 HTTPS = HTTP + SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密

二者的区别

  • HTTPS是HTTP协议的安全版本,HTTP协议的数据传输是明文的,是不安全的,HTTPS使用了SSL/TLS协议进行了加密处理,相对更安全
  • HTTP 和 HTTPS 使用连接方式不同,默认端口也不一样,HTTP是80,HTTPS是443
  • HTTPS 由于需要设计加密以及多次握手,性能方面不如 HTTP
  • HTTPS需要SSL,SSL 证书需要钱,功能越强大的证书费用越高
  • SSL:混合算法,摘要算法,数字签名

3.HTTP1.0/HTTP1.1/HTTP2.0

HTTP1.0:

  • 浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接

HTTP1.1:

  • 引入了持久连接,即TCP连接默认不关闭,可以被多个请求复用
  • 在同一个TCP连接里面,客户端可以同时发送多个请求
  • 虽然允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的,服务器只有处理完一个请求,才会接着处理下一个请求。如果前面的处理特别慢,后面就会有许多请求排队等待
  • 新增了一些请求方法
  • 新增了一些请求头和响应头

HTTP2.0:

  • 采用二进制格式而非文本格式
  • 完全多路复用,而非有序并阻塞的、只需一个连接即可实现并行
  • 使用报头压缩,降低开销
  • 服务器推送

4.HTTP状态码

HTTP状态码(英语:HTTP Status Code),用以表示网页服务器超文本传输协议响应状态的3位数字代码

状态码第一位数字决定了不同的响应状态,有如下:

  • 1 表示消息
  • 2 表示成功
  • 3 表示重定向
  • 4 表示请求错误
  • 5 表示服务器错误

1xx

代表请求已被接受,需要继续处理。这类响应是临时响应,只包含状态行和某些可选的响应头信息,并以空行结束

常见的有:

  • 100(客户端继续发送请求,这是临时响应):这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求完成后向客户端发送一个最终响应
  • 101:服务器根据客户端的请求切换协议,主要用于websocket或http2升级

2xx

代表请求已成功被服务器接收、理解、并接受

常见的有:

  • 200(成功):请求已成功,请求所希望的响应头或数据体将随此响应返回
  • 201(已创建):请求成功并且服务器创建了新的资源
  • 202(已创建):服务器已经接收请求,但尚未处理
  • 203(非授权信息):服务器已成功处理请求,但返回的信息可能来自另一来源
  • 204(无内容):服务器成功处理请求,但没有返回任何内容
  • 205(重置内容):服务器成功处理请求,但没有返回任何内容
  • 206(部分内容):服务器成功处理了部分请求

3xx

表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向

常见的有:

  • 300(多种选择):针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择
  • 301(永久移动):请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置
  • 302(临时移动): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
  • 303(查看其他位置):请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码
  • 305 (使用代理): 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理
  • 307 (临时重定向): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求

4xx

代表了客户端看起来可能发生了错误,妨碍了服务器的处理

常见的有:

  • 400(错误请求): 服务器不理解请求的语法
  • 401(未授权): 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
  • 403(禁止): 服务器拒绝请求
  • 404(未找到): 服务器找不到请求的网页
  • 405(方法禁用): 禁用请求中指定的方法
  • 406(不接受): 无法使用请求的内容特性响应请求的网页
  • 407(需要代理授权): 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理
  • 408(请求超时): 服务器等候请求时发生超时

5xx

表示服务器无法完成明显有效的请求。这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生

常见的有:

  • 500(服务器内部错误):服务器遇到错误,无法完成请求
  • 501(尚未实施):服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码
  • 502(错误网关): 服务器作为网关或代理,从上游服务器收到无效响应
  • 503(服务不可用): 服务器目前无法使用(由于超载或停机维护)
  • 504(网关超时): 服务器作为网关或代理,但是没有及时从上游服务器收到请求
  • 505(HTTP 版本不受支持): 服务器不支持请求中所用的 HTTP 协议版本

5.TCP3次握手4次挥手

三次握手其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包

主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备

过程如下:

  • 第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号
  • 第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,为了确认客户端的 SYN,将客户端的 ISN+1作为ACK的值
  • 第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,值为服务器的ISN+1。此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接
  • 第一次握手:客户端发送网络包,服务端收到了 这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
  • 第二次握手:服务端发包,客户端收到了 这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常* *
  • 第三次握手:客户端发包,服务端收到了。 这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常

通过三次握手,就能确定双方的接收和发送能力是正常的。之后就可以正常通信了

为什么不是两次握手?

如果是两次握手,发送端可以确定自己发送的信息能对方能收到,也能确定对方发的包自己能收到,但接收端只能确定对方发的包自己能收到 无法确定自己发的包对方能收到

并且两次握手的话, 客户端有可能因为网络阻塞等原因会发送多个请求报文,延时到达的请求又会与服务器建立连接,浪费掉许多服务器的资源

四次挥手

终止一个TCP连接,需要经过四次挥手

过程如下:

  • 第一次挥手:客户端发送一个 报文,客户端停止发送数据,等待服务端的确认
  • 第二次挥手:服务端收到后,会发送报文,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态
  • 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,
  • 第四次挥手:客户端收到后,一样发送一个报文作为应答。需要过一阵子确保服务端收到自己的报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态

6.地址栏输入 URL 敲下回车后发生了什么?

  • URL解析
  • DNS 查询
  • TCP 连接
  • HTTP 请求
  • 响应请求
  • 页面渲染

7. TCP和UDP的区别

  • TCP 是面向连接的协议,建立连接3次握手、断开连接四次挥手,

    UDP是面向无连接,数据传输前后不连接,发送端只负责将数据发送到网络,接收端从消息队列读取

  • TCP 提供可靠的服务,传输过程采用流量控制、编号与确认、计时器等手段确保数据无差错,不丢失。

    UDP 则尽可能传递数据,但不保证传递交付给对方

  • TCP 只能点对点全双工通信。

    UDP 支持一对一、一对多、多对一和多对多的交互通信

8.浏览器缓存

  • 浏览器缓存即 http 缓存,将请求过的数据(html、css、js)存在浏览器(本地磁盘)中,当再次访问这些资源时可以从本地直接加载,减少服务端请求
  • 服务端通过设置 http 响应头来决定缓存策略(缓存方式)

缓存流程

  • 第一次请求需要的资源,服务器返回资源的同时在 response hearder 响应头中添加了缓存策略,告诉浏览器缓存规则(比如以何种方式缓存,缓存信息…),此时就进行缓存了
  • 第二次如果是请求相同资源,那么就会检查缓存里面是否有相应资源,有的话直接取用

缓存方式

强缓存

概念:检查强缓存,不发送 http 请求直接从缓存里读取资源。一般强缓存都会设置有效时间,过期就失效

协商缓存

概念:发送 http 请求时需要携带缓存标识(tag),由服务器判断是否使用缓存。服务端会进行判断,若资源已发生变化,则返回新资源,否则告诉浏览器启用缓存即可

触发条件(两个)

  • 强缓存过期
  • Cache-Control 的值包含 no-cache

Vue.js

1.Vue

Vue.js是一个开源JS框架,也是一个创建单页应用的Web应用框架。

主要核心特性是数据驱动(MVVM) 和组件化

MVVM:

  • 模型层:负责处理业务逻辑和服务端交互
  • 视图层:负责将数据模型展示出来
  • 视图模型层:用于连接VM,视图和模型层之间的桥梁

响应式原理:

1.app.message修改数据,Vue内部是如何监听message数据改变的?

Object.defineProperty 监听对象属性的改变

const obj = {
    message:'lapi',
    name:'lala'
}
​
Object.keys(obj).forEach(key =>{
    let value = obj[key]
    Object.defineProperty(obj,key,{
        set(newValue){
            console.log('监听值的改变')
            //根据解析html代码,获取哪些人在用此属性
            value = newValue
            
            dep.notify
        },
        get(){
            console.log('获取值')
            //使用的人会用到get
            return value
        }
    })
})
​
​
//发布者订阅者模式//发布者
class Dep {
    constructor(){
        this.subs = []
    }
    addSub(watcher){
        this.subs.push(watcher)
    }
    notify(){
        this.subs.forEach(item=>{
            item.update()
        })
    }
}
​
//订阅者
class Watcher {
    constructor(name){
        this.name = name
    }
    update(){
        console.log(this,name+'update')
    }
}
​
const dep = new Dep()
const w1 = new Watcher('zs')
dep.addSub(w1)

2.当数据发生改变,Vue是如何知道要通知哪些人界面发生刷新?

发布订阅者模式

2.SPA单页应用

通过动态重写当前页面来与用户交互,避免了页面之间切换打断用户体验

在单页应用中,所有必要的代码都通过单个页加载,或者根据需要动态装载适当的资源并添加到页面页面

在任何时间点都不会重新加载,也不会转移到其他页面

首页加载慢:使用动态加载路由解决(以函数形式加载路由)

3.v-show&v-if

  • 都是用于控制元素在页面中是否显示
  • 控制手段:v-show隐藏则是为该元素添加display:none,dom元素依旧还在;v-if显示隐藏是将dom元素整个添加或删除
  • 生命周期:v-show从false到true不触发组件的生命周期
  • 如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好

4.生命周期

Vue生命周期总共可以分为8个阶段:创建前后, 载入前后,更新前后,销毁前后,以及一些特殊场景的生命周期

created,mounted,updated,destroyed

5.v-if&v-for

Vue2里,v-for优先级比v-if高

  • 不要在一个元素上同时使用v-for和v-if,会带来性能的浪费,每次渲染都会先循环在进行条件判断
  • 如果确实需要,在外层嵌套一个template,然后在外层进行v-if判断

6.data属性

在vue的实例中,data属性可以是一个对象,但在组件里data属性只能是一个函数而不能是一个对象

如果组件的属性是对象,多个组件的属性会公用一个内存地址,当一个组件的属性发生改变时也会影响到其他组件的对应属性(产生数据污染的情况);而使用函数作为data属性,因为函数返回的对象地址是不同的,就可以解决此问题。

7.双向绑定v-model

v-model将model和view进行一个双向绑定,一方发生改变时另一方也会随之改变

<!-- 使用 v-model实现双向绑定 -->
<input type="text" v-model="message">
<!-- <input type="text" :value="message" @input="message = $event.target.value"> -->

8.组件通信

  • props:父传子
  • $emit:子传父
  • ref:父组件通过ref获取子组件实例的数据
  • 事件总线 bus:通过bus:通过emit触发自定义事件,其他组件通过$on监听事件
  • vuex

9.mixin

混入,将共用的功能以对象的方式传入 mixins选项中

当组件使用 mixins对象时,所有mixins对象的选项都将被混入该组件本身的选项中来

10.slot

  • slot是Web组件内的一个占位符,后面可以用自己的标记语言来填充
  • 只能在 template里使用,但在只有默认插槽时可以在组件实例上使用

11.key

v-for中要使用key,Vue会根据key的顺序记录元素

12.keep-alive

keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM

keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们

keep-alive可以设置以下props属性:

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
  • max - 数字。最多可以缓存多少组件实例

在keep-alive中的组件会多两个生命钩子函数:activated,deactivated

13.修饰符

.stop 阻止事件冒泡

.prevent 阻止默认事件

.once 仅触发一次

.native 绑定原生事件

14.虚拟DOM

  • 一个对真实DOM的抽象,以JS对象作为基础的树,通过用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上
  • 操作DOM的代价是非常昂贵的,频繁操作还是会出现页面卡顿,影响用户的体验
  • 如果不使用虚拟DOM,每更新一个节点就要操作一次DOM,使用虚拟DOM可以在多次更新节点之后再去操作DOM,避免大量的无谓计算

15.axios

npm install axios --save

import  axios  from 'axios'//基本使用
axios({
    url:'',
    method:'GET',
    //url后面的参数
    params:{
        type:'pop',
        page:1
    }
}).then((res)=>{
    console.log(res)
})
​
//发送并发请求,返回一个数组
axios.all([axios({
    url:'',
}),axios({
   url:'',
   params:{
       type:sell,
       page:6
   }
})]).then(axios.spread((res1,res2)=>{
    console.log(res1)
    console.log(res2)
}))
​
//(results)=>{
//   console.log(results)
//}//axios实例
axios.defalut.baseURL=""
axios.defalut.timeout=5000const instance1 = axios.create({
    baseURL:'',
    timeout:5000
})
​
instance1({
    url:''
}).then(res=>{
    console.log(res)
})
​
//封装request模块
export function request(config){
    const instance = axios.creat({
        baseURL:'',
        timeout:5000
    })
    
    //axios的拦截器
    请求拦截器
    instance.interceptors.request.use(config=>{
        //请求成功时的回调
        console.log(config)
        return config
    },err=>{
        //请求失败时的回调
        console.log(err)
        return
    })
    instance.interceptors.responce.us
    
    
    //发送真正的网络请求,axios本身就是一个Promise
    return instance(config)
}
​
request({
    url:''
}).then(res=>{
    console.log(res)
}).catch(err=>{
    console.log(err)
})

16.跨域

JSONP: 将获取数据以参数传入函数,执行函数后将返回值传递给你

后端实现了CORS就可以实现跨域

17.history与hash

最明显的区别:hash在url中会有#

hash虽出现在URL中,但不会被包含在http请求中,对后端完全没有影响,因此改变hash不会使页面重新加载

history利用了pushState()和replaceState()方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录的修改功能,虽然会改变URL,但浏览器不会立刻向后端发送请求

CSS

1.回流重绘

当页面中的一部分元素需要改变规模尺寸、布局、显示隐藏等,页面重新构建,此时就是回流。所有页面第一次加载时需要产生一次回流

display:none和visibility:hidden的区别是:

1.display:none是彻底消失,不在文档流中占位,浏览器也不会解析该元素;visibility:hidden是视觉上消失了,可以理解为透明度为0的效果,在文档流中占位,浏览器会解析该元素;

2.使用visibility:hidden比display:none性能上要好,display:none切换显示时,页面产生回流,而visibility切换是否显示时则不会引起回流。

2.如何实现垂直居中

1.对于块级元素

  • 绝对定位,然后top:50%,margin-top:-height/2或者transform:translate(-50%,-50%); /针对元素本身向左以及向上移动50%/
  • 绝对定位,top:0,bottom:0,height给定,margin:auto
  • display: table-cell; vertical-align: middle;
  • 父元素开启flex ,子元素margin:auto/justfy-content/align-items

2.line-height

3.两种盒子模型

标准盒子模型

怪异盒子模型

Box-sizing:content-box/border-box

Webpack

  • webpack是前端开发中的模块化打包工具(什么是模块化)(什么是打包)
  • 模块化:模块是指能独立完成一定功能的代码集合,模块化就是将实现不同功能的代码划分为不同的模块,以便代码的复用与管理
  • 打包:就是将webpack中各种资源模块进行打包合并成一个或多个包
  • gulp更强调自动化与定义一些任务;而webpack更强调模块化,其他的只是一些附加功能
  • webpack为了可以正常运行,必须依赖node环境
  • node环境为了执行代码,必须包含其中各种依赖的包
  • 使用npm工具(node packages manager)管理node环境下的各种包
  • 通过webpack.config.js对webpack进行配置(entry,output),package.json中配置执行webpack的快捷操作

loader

处理除JS外的其他资源,如css、图片等,Webpack无法转化为.js文件,因此要使用各种loader将其他格式的文件转化为.js文件

  • 同样在webpack.config.js中配置,module:{rules:[{test:'',use:['']}]}

    module.rules是一个数组[],每个元素是一个对象,有test为匹配规则,use针对匹配到的文件,调用对应的loader来使用,对于多个loader,use是从右向左读的

  • css-loader,less-loader,url-loader,bable-loader等

plugin

插件,用于对webpack现有功能进行扩展

  • 与loader的区别:loader用于转换某些类型的模块,是一个转换器 、加载器;plugin是对webpack本身的扩展,是一个扩展器

  • 使用过程:1. 通过 npm安装需要使用的plugin 2. 在webpack.config.js中配置插件

  • 常用的plugin:

    • BannerPlugin:添加说明信息
    • HtmlWebpackPlugin:打包后自动生成一个html文件
    • UglifyWebpackPlugin:压缩丑化

热更新

安装 webpack-dev-server

webpack-dev-server 创建两个服务器:提供静态资源的服务(express)和Socket服务

  • express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
  • socket server 是一个 websocket 的长连接,双方可以通信
  • 当 socket server 监听到对应的模块发生变化时,会生成.json和.js文件
  • 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
  • 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新