[TOC]

1. ES6 的新特性

  • let (声明变量**)**
  • const (声明常量**,**常量不能修改的量)
  • class (**创建类**)
  • import / export (基于 **ES6 的模块规范创建导入/导出模块(文件/组件))**
  • new set (数组去重**)**
  • Symbol (唯一的值**) ** var a = Symbol(‘qqq’)
  • …ary (展开运算符、剩余运算符**)**
  • ${}** 模板字符串**
  • 解构赋值** let {a} = obj; let [b] = ary**
  • for of 循环
  • ()=>{} 箭头函数
  • 数组新增方法:some every filter reduce …
  • 对象新增方法: Object.assign() Object.values() Object.keys() Object.create()
  • 1.1 var、let、const的区别

  1. let 和 const 声明变量不存在变量提升,如果要使用这个变量,我们需要在变量定义之后使用;
  2. let 和 const 不能重复声明变量,如果重复声明会报错;
  3. 用 let 和 const 在全局声明变量不会给 window 增加属性;
  4. let 和 const 出现在代码块中,会把代码块(字面量声明对象除外)变成块级作用域,并且出现暂时性死区

1.2 箭头函数与普通函数的区别:

  1. 箭头函数是匿名函数,不能作为构造函数,不能使用 new
  2. 箭头函数没有原型属性
  3. this 指向不同,箭头函数的 this 是定义时所在的对象,普通函数看前面有没有, .点前面是谁 this 就是谁,没有.就是 window
  4. 不可以使用 arguments 对象,该对象在函数体内不存在。

2. JS的数据类型:

2.1 基本数据类型

  • number 数字;
  • boolean 布尔值 :有两个值 true、false ;
  • string 字符串
  • null 空对象;
  • undefined 未定义的值(很多浏览器的初始值是undefined)
  • Sy**mbol()** 产生一个唯一的值,和谁都不重复

[ null **和 undefined 的区别: ]**

  • null 是一个表示”无”的对象,转为数值时为 0
  • undefined 是一个表示”无”的原始值,转为数值时为 NaN
  • 当声明的变量还未被初始化时,变量的默认值为 undefined
  • null 用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象

undefined** **表示 “缺少值”,就是此处应该有一个值,但是还没有定义。
典型用法是:
1. 变量被声明了,但没有赋值时,就等于 undefined
2. 调用函数时,应该提供的参数没有提供,该参数等于 undefined
3. 对象没有赋值的属性,该属性的值为 undefined
4. 函数没有返回值时,默认返回 undefined
null** **表示“没有对象”,即该处不应该有值。
典型用法是:
1. 作为函数的参数,表示该函数的参数不是对象
2. 作为对象原型链的终点

2.2 引用数据类型:

  • 对象
    • 普通对象
    • 数组对象
    • 正则对象(匹配字符串的规则)
    • 日期对象
    • 函数对象

[ 对象的存储过程: ]
1. 开辟一个空间地址
2. 把键值对存储到这个空间地址的堆内存中
3. 把这个对象指针赋值给变量名

let obj = { 
  a:1, 
  fn:(function (val) {
    // 赋给fn的是自执行函数的执行结果
也就是一个undefined
    // 该自执行函数只会执行一次
    console.log(val);
  })(obj.a) 
};
let obj2 = obj;// 两者代表了同一个地址;
// 获取属性的值 obj.fn 或者 obj['fn']
// 新增属性: obj.c = 100 或者 obj['c'] = 100
// 真删除 delete obj.a (在严格模式下不支持该方法); 假删除: obj.a = null;

// 引用类型小习题
let a = 3;
let b = new Number(3);
let c = 3;
console.log(a == b);
console.log(a === b);
console.log(b === c);
//=========================
const a = {};
const b = { key: "b" };
const c = { key: "c" };
a[b]
= 123;
a[c]
= 456;
console.log(a[b]);

2.3 基本数据类型与引用数据类型的区别

基本数据类型是操作值,引用数据类型操作的是堆内存空间地址,

布尔值转换: 0 NaN ‘’ null undefined 转化成布尔值是 false,其余的都是 true
检验有效数字的方法:isNaN
常用的数据类型检测方式: typeof constructor instanceof Object.prototype.toString.call()

2.4 比较运算符

== 相对比较:会进行默认的类型转化; 若转换之后的值相等,则结果就是 true
=== 绝对比较,值不但要相同、类型也得相同。
引用数据类型之间的比较,就看是不是同一个地址;

2.5 逻辑运算符

|| 表示或者,前边成立给前边,前边不成立给后边
&& 表示并且前边成立给后边,前边不成立给前边


3. 定义函数的方法

3.1 function 声明

//ES5
function getSum(){}
function (){}//匿名函数
//ES6
()=>{}

3.2 函数表达式

//ES5
var getSum=function(){}
//ES6
let getSum=()=>{}

3.3 构造函数

const getSum = new Function('a', 'b' , 'return a + b')

4. JS 作用域的理解

JS 中的作用域分为两种:

  • 全局作用域
  • 函数作用域。

函数作用域中定义的变量,只能在函数中调用,外界无法访问。
没有块级作用域导致了 if 或 for 这样的逻辑语句中定义的变量可以被外界访问,
因此 ES6 中新增了 let 和 const 命令来进行块级作用域的声明。

//循环绑定的问题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
//作用域链  变量的查找机制
// 上级作用域  函数在哪里定义的,那么该函数执行形成的作用的上级作用域就是谁
// 了解了上级作用域, 就比较容易查找变量对应的值


5.闭包的理解

简单来说闭包就是在函数里面声明函数,本质上说就是在函数内部和函数外部搭建起一座桥梁,使得子函数可以访问父函数中所有的局部变量,但是反之不可以,这只是闭包的作用之一,另一个作用,则是保护变量不受外界污染,使其一直存在内存中,在工作中我们还是少使用闭包的好,因为闭包太消耗内存,不到万不得已的时候尽量不使用。

6. 数组

6.1 数组去重

1、双 for 循环去重
2、利用对象的属性名不能重复去重
3、利用 es6 的 Set 不能重复去重

(具体代码自己查)

6.2 数组重组 (将 name 值相同的合并,并去除 age 的属性)

let ary = [
    {name:1,age:2,number:1,son:'son1'},
    {name:2,age:23,number:2,son:'son2'},
    {name:2,age:22,number:3,son:'son3'},
    {name:1,age:12,number:4,son:'son4'},
    {name:1,age:42,number:5,son:'son5'}
]
fn(ary)
// 结果为
[
  {
    "name":1,
    "list":[{"number":1,"son":"son1"},{"number":4,"son":"son4"},{"number":5,"son":"son5"}]
  },
  {
    "name":2,
    "list":[{"number":2,"son":"son2"},{"number":3,"son":"son3"}]
  }
]
function fn(ary){
    let arr = [];
    ary.forEach(item=>{
        let bol = arr.some(val=>{
            if(val.name===item.name){
                let obj = {};
                Object.keys(item).forEach(v=>{
                    if(v!='name'&&v!='age'){
                        obj[v] = item[v]
                    }
                })
                val.list.push(obj);
                return true
            }
        })
        if(!bol){
            let obj = {};
            Object.keys(item).forEach(v=>{
                if(v!='name'&&v!='age'){
                    obj[v] = item[v]
                }
            })
            arr.push({name:item.name,list:[obj]});
        }
    })
    return arr;
}
fn(ary)


6.3 数组扁平化

var arr = [
  [1, 2, 2],
  [3, 4, 5, 5],
  [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];

function flat1(arr) {
  let temp = [];
  function fn(ary) {
    ary.forEach(item => {
      if (typeof item == 'object') {
        fn(item)
      } else {
        temp.push(item)
      }
    })
  }
  fn(arr)
  return temp;
}

function flat2() {
  return [].concat(...this.map(item => (Array.isArray(item) ? item.flat2() : [item])));
}

3.png


7. 原型及原型链

7.1 原型

  • 函数都带有一个 prototype 属性,这是属性是指向构造函数的原型对象,这个对象包含所有实例共享的属性和方法。
  • 原型对象都有一个 constructor 属性,这个属性指向所关联的构造函数。
  • 每个对象都有一个 proto 属性[非标准的方法],这个属性指向构造函数的原型 prototype

    7.2 原型链

  • 当访问实例对象的某个属性时,会先在这个对象本身的属性上查找,如果没有找到,则会 通过 proto 属性去原型上查找,如果还没有 找到则会在构造函数的原型的 proto 中查找, 这样一层层向上查找就会形成一个作用域链,称为原型链

    7.3 原型相关习题

    [ 第一题 ]

function Fn() {
    this.x = 100;
    this.y = 200;
    this.getX = function () {
        console.log(this.x);
    }
}
Fn.prototype = {
    y: 400,
    getX: function () {
        console.log(this.x);
    },
    getY: function () {
        console.log(this.y);
    },
    sum: function () {
        console.log(this.x + this.y);
    }
};
var f1 = new Fn();
var f2 = new Fn;
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.__proto__.getX === f2.getX);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();
f1.sum();
Fn.prototype.sum();


[ 第二题 ]

function Foo() {
    getName = function () {console.log(1);};
    return this;
}
Foo.getName = function () {console.log(2);}; 
Foo.prototype.getName = function ()
{console.log(3);};
var getName = function () {console.log(4);};
function getName() {console.log(5);}

Foo.getName();
getName();
Foo().getName(); 
getName();
var a = new Foo.getName(); // 
var b = new Foo().getName();
var c = new new Foo().getName();
console.log(a,b,c);


[ 第三题 ]

function Person() {
    this.name = 'zhufeng'
};
Person.prototype.getName = function ()
{
    console.log(this.name)
    console.log(this.age)
};
Person.prototype.age = 5000;

var per1 = new Person;
per1.getName();
per1.age = 9;
per1.getName();
console.log(per1.age);
var per2 = new Person;
console.log(per2.age);

7.4 Object.create 的作用

let obj = {a:123};
let o = Object.create(obj);
//该函数返回了一个新的空对象,但是该空对象的__proto__是指向了obj这个参数
// 手写Object.create
function create(proto) {
  function F() {}
  F.prototype = proto;

  return new F();
}

7.5 new 的执行过程是怎么回事?

new 操作符做了这些事:

  • 它创建了一个全新的对象
  • 它会被执行 [[Prototype]](也就是 proto)链接
  • 它使 this 指向新创建的对象
  • 通过 new 创建的每个对象将最终被 [[Prototype]] 链接到这个函数的 prototype 对象上
  • 如果函数没有返回对象类型 Object (包含 Functoin, Array, Date, RegExg, Error),那么 new 表达式中的函数调用将返回该对象引用

[ 模拟 new ]

function objectFactory() {
  const obj = new Object();
  const Constructor = [].shift.call(arguments);

  obj.__proto__ = Constructor.prototype;

  const ret = Constructor.apply(obj, arguments);

  return typeof ret === "object" ? ret : obj;
}

7.6 call, apply, bind 三者的区别?

[ apply() 方法 ]
**

  • apply() 方法调用一个函数, 其具有一个指定的 this 值,以及作为一个数组(或类似数组的对象)提供的参数fun.apply(thisArg, [argsArray]
  • apply 和 call 基本类似,他们的区别只是传入的参数不同。
  • apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。

[ 模拟 apply ]
**

Function.prototype.myapply = function(context, arr) {
  var context = Object(context) || window;
  context.fn = this;

  var result;
  if (!arr) {
    result = context.fn();
  } else {
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    result = eval("context.fn(" + args + ")");
  }

  delete context.fn;
  return result;
};

[ bind() 方法 ]

  • bind() 方法创建一个新的函数, 当被调用时,将其 this 关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

[ bind() 方法的实现 ]
实现 bind 要做什么

  • 返回一个函数,绑定 this,传递预置参数
  • bind 返回的函数可以作为构造函数使用。故作为构造函数时应使得 this 失效,但是传入的参数依然有效
// mdn的实现
if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying
to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function()
{},
        fBound  = function() {
          // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 维护原型关系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    fBound.prototype = new fNOP();

    return fBound;
  };
}


[ call() 方法 ]

  • 将函数设为对象的属性
  • 执行 & 删除这个函数
  • 指定 this 到函数并传入给定参数执行函数
  • 如果不传入参数,默认指向为 window


    [ call() 方法的实现 ]

Function.prototype.myCall = function(context) {
  //此处没有考虑context非object情况
  context.fn = this;
  let args = [];
  for (let i = 1, len = arguments.length; i < len; i++) {
    args.push(arguments[i]);
  }
  context.fn(...args);
  let result = context.fn(...args);
  delete context.fn;
  return result;
};

7.7 实现类的继承

类的继承在几年前是重点内容,有n种继承方式各有优劣,es6 普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。

function Parent(name) {
    this.parent = name
}
Parent.prototype.say = function() {
    console.log(`${this.parent}: 你打篮球的样子像kunkun`)
}
function Child(name, parent) {
    // 将父类的构造函数绑定在子类上
    Parent.call(this, parent)
    this.child = name
}
/** 
 1. 这一步不用Child.prototype =Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类
 2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性
3.
Object.create是创建了父类原型的副本,与父类原型完全隔离
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {
    console.log(`${this.parent}好,我是练习时长两年半的${this.child}`);
}
// 注意记得把子类的构造指向子类本身
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.say() // father: 你打篮球的样子像kunkun
var child = new Child('cxk', 'father');
child.say() // father好,我是练习时长两年半的cxk

7.8 谈谈你对 this 指向的理解

this 的指向,始终坚持一个原理:this **永远指向最后调用它的那个对象**
改变 this 的指向我总结有以下几种方法:

  • 使用 ES6 的箭头函数
  • 在函数内部使用 _this = this
  • 使用 apply、call、bind
  • new 实例化一个对象

全局作用域下的 this 指向 window
如果给元素的事件行为绑定函数,那么函数中的 this 指向当前被绑定的那个元素
函数中的 this,要看函数执行前有没有 . , 有 . 的话,点前面是谁,this 就指向谁,如果没有点,指向 window
自执行函数中的 this 永远指向 window
定时器中函数的 this 指向 window
构造函数中的 this 指向当前的实例
call、apply、bind 可以改变函数的 this 指向
箭头函数中没有 this,如果输出 this,就会输出箭头函数定义时所在的作用域中的 this


8. DOM

1).新建节点
document.createElement(“元素名”) // 新建一个元素节点
document.createAttribute(“属性名”) // 新建一个属性节点
document.createTextNode(“文本内容”) // 创建一个文本节点
document.createDocumentFragment() // 新建一个 DOM 片段
2).添加、移除、替换、插入:
appendChild() // 向节点的子节点末尾添加新的子节点
removerChild() // 移除
parentNode.replaceChild(newChild, oldChild );用新节点替换父节点中已有的子节点
insertBeform() // 在已有的子节点前插入一个新的子节点
3).查找
document.getElementById() // 通过元素 id 查找,唯一性
document.getElementByClassName() // 通过 class 名称查找
document.getElementsByTagName() // 通过标签名称查找
document.getElementsByName() // 通过元素的 Name 属性的值查找
—————————————————————————————————————

8.1 DOM 回流、重绘

DOM 回流 (reflow):页面中的元素增加、删除、大小、位置的改变,会引起浏览器重新计算 其他元素的位置,这种现象称为 DOM 回流。DOM 回流非常消耗性能,尽量避免 DOM 回流
DOM 重绘:元素的某些 css 样式如背景色、字体颜色等发生改变时,浏览器需要重新描绘渲 染这个元素,这种现象称为 DOM 重绘。

8.2 DOM 操作的读写分离:

在 JS 中把设置样式和获取样式的两种操作分来来写, 设置样式的操作放在一起,读取样式的操作放在一起,这样可以有效的减少 DOM 的回流和重绘;

8.3 DOM事件:

事件的传播机制:先冒泡,然后是目标阶段 然后再去捕获,我们可以利用事件的冒泡来进行事件委托,、也就是可以在父元素上绑定事件,通过事件对象 e 来判断点击的具体元素;可以提供性能;
我们可以利用的 e.stopPropagation()来阻止冒泡;利用 e.preventDefault() 来阻止默认事件;
事件中有 0 级事件绑定和 2 级事件绑定

8.4 JS 盒子模型

  • // client offset scroll width height left top
  • // clientWidth 内容宽度 + 左右 padding
  • // offsetWidth clientWidth + 左右 border
  • // offsetTop 当前盒子的外边框到上级参照物的内边框的偏移量
  • // offsetParent 上级参照物:有定位的上级(包含 父级,祖父,曾祖父…)元素,所有所有上级都没有定位, 则参照物就是 body
  • // scroll 内容不溢出 等同于 client
  • // 内容溢出时 没有设置 overflow 值是内容宽高 + 上或左 padding
  • // 内容溢出时 有设置 overflow 时 值是内容宽高 + 上下或左右 padding
  • // scrollTop 卷去内容的高度
  • // 13 个属性 只有 scrollTop 和 scrollLeft 时可以设置值的, 其他的都是只读属性

9. JS 的异步编程

因为js是单线程的。浏览器遇到 etTimeout 和 setInterval 会先执行完当前的代码块,在此之前会把定时器推入浏览器的待执行时间队列里面,等到浏览器执行完当前代码之后会看下事件队列里有没有任务,有的话才执行定时器里的代码
常用的方式:setTimeout setIntervel ajax Promise asyc/await

宏任务 (marcotask) 微任务 (microtask) 的执行顺序:
先执行宏任务,然后在执行微任务;

JS 中的宏任务:setTimeout setIntervel ajax
JS 中的微任务:Promise.then Promise.catch await (可以理解成 Promise.then)
JS 的执行顺序是先同步 再异步;同步执行完成之前 异步不会执行
EventLoop 事件循环
EventQueue 事件队列

9.1 异步编程相关练习

[ 第一题 ]

async function async1() {
console.log("async1 start");
await  async2();
console.log("async1 end");
}
async  function async2() {
console.log( 'async2');
}
console.log("script start");
setTimeout(function () {
console.log("settimeout");
},0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log('script end');

[ 第二题 ]

async function async1() {
console.log("async1 start");
await  async2();
console.log("async1 end");
}
async  function async2() {
console.log( 'async2');
}
console.log("script start");
setTimeout(function () {
console.log("settimeout");
});
async1()
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
setImmediate(()=>{
console.log("setImmediate")
})
process.nextTick(()=>{
console.log("process")
})
console.log('script end');

10. 正则

10.1 解析 URL Params 为对象

var str = 'http://www.zhufengpeixun.cn/?lx=1&from=wx&b=12&c=13#qqqq';
function getParam(url){
var reg = /([?=&]+)=([?=&#]+)/g;
let obj = {};
url.match(reg).forEach(item=>{
let a = item.split('='); // ['lx','1']
obj[a[0]] = a[1]
})
return obj
}
getParam(str);

10.2 模板引擎实现

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined
function render(template, data) {
const reg = /{{(\w+)}}/; // 模板字符串正则
if (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的结构
}
return template; // 如果模板没有模板字符串直接返回
}

10.3 出现次数最多的字符

var str = 'sfgsdfgsertdgfsdfgsertwegdsfgertewgsdfgsdg';
function getMax2(str) {
str = str.split('').sort().join('');// 把字符串进行排序
let key = '',num = 0;
str.replace(/(\w)\1*/g,function($0,$1){
if($0.length > num){
num = $0.length;
key = $1;
}
})
return{
key,num
}
}
getMax2(str);

10.4 千分符的实现

// 100,000,00

//[ 方法1 ]

var str = '1234567'; // 1,234,567
function moneyFormate(str){
str = str.split('').reverse().join('')
let s = '';
for(let i = 0; i < str.length ; i++){
i%3 == 2 ? s+=str[i]+',' : s+=str[i]
}
s = s.split('').reverse().join('')
return s
}
moneyFormate(str);// 1,234,567

//[ 方法2 ]

var str = '1234567';
function moneyFormate2(str){
let s = '';
// s = str.replace(/\d{1,3}(?=(\d{3})+$)/g,function(a){
//     console.log(arguments)
//     return a + ','
// })
s = str.replace(/(\d{1,3})(?=(\d{3})+$)/g,'$1,');
return s;
}
moneyFormate2(str);

var str = '   sdfgsg   fsgfsd    ';
// 使用正则去除字符串的首尾空格
// 以 1 到 多个 空格开头或者结尾的 都替换成空;
var res = str.replace(/^ +| +$/g,'')

11. http & ajax

11.1 TCP / IP 的三次握手和四次挥手

三次握手:

第一次握手:客户端向服务端发送 SYN 码数据包,表示客户端要求和服务端建立连接;
第二次握手:服务端收到客户端的连接请求后,会发送 ACK 数据包给客户端,表示你的连接请求已经收到,询问客户端是否真的需要建立连接;
第三次握手:客户端收到 ACK 码以后会检验是否正确,如果正确,客户端会再次发送 ACK 码给服务端,表示确认建立连接; (三次握手都成功以后才会建立连接,然后才会发送数据;)

四次挥手:
**
第一次挥手:当客户端发送数据结束后,会发送 FIN 码数据包给服务端,表示告知服务端客 户端的数据已经传递完了。
第二次挥手:当服务端收到 FIN 后,会发送 ACK 给客户端,表示服务端已经知道客户端传完 了。客户端收到ACK 以后就会把传递数据给服务端的通道关闭;
第三次挥手:当服务端把响应的数据发送完毕后,会发送一个 FIN 给客户端,告知客户端响 应的数据已经发送完毕;
第四次挥手:当客户端收到 FIN 后,会发送一个 ACK 码数据包给服务端,告知服务端客户端已 经知道数据发送完毕;服务端收到 ACK 码后,可以安心的把数据传递通道关闭掉。

11.2 http 常用状态码 (http-status-code):

2xx:表示成功
200 OK 表示所有东西都正常
204 表示请求成功,但是服务端没有内容给你
3xx: 表示重定向
301 永久重定向(当访问一个永久重定向的网站的时候,一个域名被指向一个其他网站,且是永久的)
302 临时重定向
304 走缓存(服务端觉得你之前请求过这个东西,而且服务器上的那一份没有发生变化,告诉客户端用缓存 就行)

  • 301,Moved Permanently。永久重定向,该操作比较危险,需要谨慎操作:如果设置了301,但是一段时间后又想取消,但是浏览器中已经有了缓存,还是会重定向。
  • 302,Fount。临时重定向,但是会在重定向的时候改变 method: 把 POST 改成 GET,于是有了 307
  • 307,Temporary Redirect。临时重定向,在重定向时不会改变 method

4xx: 表示客户端错误
400 参数传递不当,导致的错误
401 权限不够导致的
403 服务端已经理解请求,但是拒绝响应
404 客户端请求的资源或者数据不存在(发现请求接口 404, 有两种情况一种是咱们写错接口了或者服 务端还没部署)
5xx: 表示服务端错误(遇到以5开头的错误去找服务端错误)
500 服务端内部错误
502 网关错误

11.3 从浏览器输入 URL 按回车到页面显示都发生了什么?

  • 浏览器根据 URL 进行 DNS 查询
  • 首先从 DNS 缓存中查询
  • 若未在缓存中找到,则不停的向上一级级请求 DNS 服务器
  • 取得 IP 地址,建立 TCP 连接
  • 构造 HTTP 请求报
  • 添加一些 HTTP 首部
  • 根据同源政策添加 cookie
  • 在 TCP 连接上发送 HTTP 报文,等待响应
  • 服务器处理 HTTP 请求报文,返回响应 HTTP 响应报文
  • 浏览器处理服务器返回的 HTTP 响应报文,若为 HTML 则渲染页面,不包括脚本的简单渲染流程如下
  1. 解析 DOM、CSSOM
    2. 根据 DOM、CSSOM 计算 render tree
    3. 根据 render tree 进行 layout
    4. paint,至此,用户可以看到页面了

    11.4 HTTPS 和 HTTP 的区别主要如下?

    HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,要比 http 协议安全。
    1、https 协议需要到 ca 申请证书,一般免费证书较少,因而需要一定费用。
    2、http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议。
    3、http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
    4、http 的连接很简单,是无状态的;HTTPS 协议是由 SSL + HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全。

https **主要解决三个安全问题:**

  • 内容隐私
  • 防篡改
  • 确认对方身份

https 并不是直接通过非对称加密传输过程,而是有握手过程,握手过程主要是和服务器做通讯,生成私有秘钥,最后通过该秘钥对称加密传输数据。还有验证证书的正确性。 证书验证过程保证了对方是合法的,并且中间人无法通过伪造证书方式进行攻击。

11.5 浏览器缓存?

强缓存:不会向服务器发送请求,直接从缓存中读取资源,在 chrome 控制台的 Network 选项中可以看到该请求返回 200 的状态码,并且 Size 显示 from disk cache 或 from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。
协商缓存:就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
协商缓存生效,返回 304 和 Not Modified
协商缓存失效,返回 200 和请求结果协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。
强制缓存优先于协商缓存进行,若强制缓存 (Expires 和 Cache-Control) 生效则直接使用缓存,若不生效则进行协商缓存 (Last-Modified / If-Modified-Since 和 Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回 200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回 304,继续使用缓存

11.6 ajax四步

  1. 创建 XMLHttpRequest 对象,也就是创建一个异步调用对象
    2. 创建一个新的 HTTP 请求,并指定该 HTTP 请求的方法、URL 及验证信息
    3. 设置响应 HTTP 请求状态变化的函数
    4. 发送 HTTP 请求

    11.7 你使用过哪些 ajax?

    从原生的 XHR 到 jquery ajax,再到现在的 axios 和 fetch。
    axios 和 fetch 都是基于 Promise 的,一般我们在使用时都会进行二次封装
    讲到 fetch 跟 jquery ajax 的区别,这也是它很奇怪的地方
    当接收到一个代表错误的 HTTP 状态码时,从 fetch() 返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ), 仅当网络故障时或请求被阻止时,才会标记为 reject。 默认情况下, fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项)

    11.8 一般我们再拦截器中都会写什么代码?

    请求拦截中我们一半会把 token 写在这里,这样的话就不用每次请求都要写这个参数
    还会做一个数据格式的处理,假如某个参数需要统一处理 可以放在这里,
    响应拦截一半会做一个判断 请求失败的话直接调用失败提示框 这样不用每个接口都写同样的代码
    也会再 return 时 return reponse.data; 这样就可以不用每个数据接受的时候都加一个 data.data

    11.9 get 请求和 post 请求有什么区别?什么时候使用 post?

GET:一般用于信息获取,使用 URL 传递参数,对所发送信息的数量也有限制,一般在 2000 个字符
POST:一般用于修改服务器上的资源,对所发送的信息没有限制
在以下情况中,请使用 POST 请求: 1. 无法使用缓存文件(更新服务器上的文件或数据库) 2. 向服务器发送大量数据(POST 没有数据量限制) 3. 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠
实际上 HTTP 协议从未规定 GET / POST 的请求长度限制是多少。对 get 请求参数的限制是来源与浏览器或 web 服务器,浏览器或 web 服务器限制了 url 的长度。为了明确这个概念,我们必须再次强调下面几点:
1、HTTP 协议 未规定 GET 和 POST 的长度限制
2、GET 的最大长度显示是因为 浏览器和 web 服务器限制了 URI 的长度
3、不同的浏览器和 WEB 服务器,限制的最大长度不一样
4、要支持 IE,则最大长度为 2083byte,若只支持 Chrome,则最大长度 8182byt

11.10 Cookie 和 Session 的区别?


  • 安全性: Session 比 Cookie 安全,Session 是存储在服务器端的,Cookie 是存储在客户端的。
  • 存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型。
  • 有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
  • 存储大小不同: 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。

6.png

11.11 Token 相关?

  1. 客户端使用用户名跟密码请求登录
    2. 服务端收到请求,去验证用户名与密码
    3. 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
    4. 客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
    5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 token
    6. 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据
    · 每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里
    · 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库
    · token **完全由应用管理,所以它可以避开同源策略**

    11.12 同源策略

    同源策略是客户端脚本(尤其是 Javascript)的重要的安全度量标准。其目的是防止某个文档或脚本从多个不同源装载。 这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议,指一段脚本只能读取来自同一来源的窗口和文档的属性。
    为什么要有同源限制?
    我们举例说明:比如一个黑客程序,他利用 Iframe 把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过 Javascript 读取到你的表单中 input 中的内容,这样用户名,密码就轻松到手了

    11.13 工作中是怎么解决跨域的?

    1.jsonp
    1) JSONP 原理
    利用 **