2020年Web前端面试题之原生JS(最全,最详细)

原生JS

1.ES6的新特性:

let(声明变量)
const(声明常量,常量不能修改的量)
var、let、const的区别
1. let和const声明变量不存在变量提升,如果要使用这个变量,我们需要在变量定义之后使用;
2. let和const不能重复声明变量,如果重复声明会报错;
3. 用let 和 const 在全局声明变量不会给window增加属性;
4. let和const出现在代码块中,会把代码块(字面量声明对象除外)变成块级作用域,并且出现暂时 性死区 class(创建类)
import/export(基于ES6的模块规范创建导入/导出模块(文件/组件))
new set(数组去重)
Symbol(唯一的值) ** var a = Symbol(‘qqq’)
…ary(展开运算符、剩余运算符)

${} 模板字符串 **
解构赋值 let {a} = obj; let [b] = ary
for of 循环
()=>{} 箭头函数

箭头函数与普通函数的区别:
1. 箭头函数是匿名函数,不能作为构造函数,不能使用new
2. 箭头函数没有原型属性
3.this指向不同,箭头函数的this是定义时所在的对象,普通函数看前面有没有.,点前面是谁this 就是谁,没有.就是window
4. 不可以使用arguments对象,该对象在函数体内不存在。
**数组新增方法:**some every filter reduce …
对象新增方法: Object.assign() Object.values() Object.keys() Object.create()…



2.JS的数据类型:

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

null和undefined的区别:
null 是一个表示"无"的对象,转为数值时为 0
undefined 是一个表示"无"的原始值,转为数值时为 NaN  
  当声明的变量还未被初始化时,变量的默认值为 undefined
null 用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象  
undefined 表示 “缺少值”,就是此处应该有一个值,但是还没有定义。
  典型用法是:
  1. 变量被声明了,但没有赋值时,就等于 undefined
  2. 调用函数时,应该提供的参数没有提供,该参数等于 undefined
  3. 对象没有赋值的属性,该属性的值为 undefined
  4. 函数没有返回值时,默认返回 undefined  
null 表示“没有对象”,即该处不应该有值。
  典型用法是:
  1. 作为函数的参数,表示该函数的参数不是对象
  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]);

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

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

比较运算符:
== 相对比较:会进行默认的类型转化; 若转换之后的值相等,则结果就是true
=== 绝对比较,值不但要相同、类型也得相同。
引用数据类型之间的比较,就看是不是同一个地址;
逻辑运算符:
|| 表示或者,前边成立给前边,前边不成立给后边
&& 表示并且前边成立给后边,前边不成立给前边


3.定义函数的方法

1.function声明

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


2.函数表达式

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

3.构造函数

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

4.JS作用域的理解

JS中的作用域分为两种:
	全局作用域和函数作用域。
  函数作用域中定义的变量,只能在函数中调用,外界无法访问。
  没有块级作用域导致了iffor这样的逻辑语句中定义的变量可以被外界访问,
  因此ES6中新增了letconst命令来进行块级作用域的声明。
  
  //循环绑定的问题
  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.数组

// 数组去重
1、双for循环去重
2、利用对象的属性名不能重复去重
3、利用es6的Set不能重复去重
(具体代码自己查)
// 数组重组  (将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)


// 数组扁平化
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])));
}

image.png

7.原型及原型链

原型
- 函数都带有一个prototype 属性,这是属性是指向构造函数的原型对象,这个对象包含所有实例共享的属性和方法。
- 原型对象都有一个constructor 属性,这个属性指向所关联的构造函数。
- 每个对象都有一个__proto__ 属性[非标准的方法],这个属性指向构造函数的原型 prototype
原型链
- 当访问实例对象的某个属性时,会先在这个对象本身的属性上查找,如果没有找到,则会 通过 proto 属性去原型上查找,如果还没有 找到则会在构造函数的原型的__ proto__中查 找, 这样一层层向上查找就会形成一个作用域链,称为原型链
原型相关习题

// 第一题
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);


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();
}


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;
}

call,apply,bind三者的区别?
apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数fun.apply(thisArg, [argsArray])

apply 和 call 基本类似,他们的区别只是传入的参数不同。
apply 和 call 的区别是 call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。

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

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;
};

// 模拟 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要做什么
返回一个函数,绑定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;
  };
}

实现类的继承
类的继承在几年前是重点内容,有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


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

  • 使用 ES6 的箭头函数
  • 在函数内部使用 _this = this
  • 使用 applycallbind
  • 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属性的值查找
--------------------------------------------------------------------------------
DOM回流、重绘
DOM回流(reflow):页面中的元素增加、删除、大小、位置的改变,会引起浏览器重新计算 其他元素的位置,这种现象称为DOM回流。DOM回流非常消耗性能,尽量避免DOM回流
DOM重绘:元素的某些css样式如背景色、字体颜色等发生改变时,浏览器需要重新描绘渲 染这个元素,这种现象称为DOM重绘。


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


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




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是单线程的。浏览器遇到setTimeout 和 setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的待执行时间队列里面,等到浏览器执行完当前代码之后会看下事件队列里有没有任务,有的话才执行定时器里的代码
常用的方式:setTimeout setIntervel ajax Promise asyc/await
宏任务(marcotask)微任务(microtask) 的执行顺序
先执行微任务,然后在执行宏任务;
JS中的宏任务:setTimeout setIntervel ajax
JS中的微任务:Promise.then Promise.catch await(可以理解成Promise.then)
JS的执行顺序是先同步 再异步;同步执行完成之前 异步不会执行
EventLoop 事件循环
EventQueue 事件队列

// 第一题
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.正则

//解析 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);
//=================================================

//模板引擎实现
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; // 如果模板没有模板字符串直接返回
}
//=================================================


// 出现次数最多的字符
 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);
//=================================================


// 千分符的实现
// 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,'')

10.http&ajax

1.TCP/IP的三次握手和四次挥手
三次握手:
第一次握手:客户端向服务端发送SYN码数据包,表示客户端要求和服务端建立连接;
第二次握手:服务端收到客户端的连接请求后,会发送ACK数据包给客户端,表示你的连接 请求已经收到,询问客户端是否真的需要建立连接;
第三次握手:客户端收到ACK码以后会检验是否正确,如果正确,客户端会再次发送ACK码给 服务端,表示确认建立连接; (三次握手都成功以后才会建立连接,然后才会发送数据;)
四次挥手:
第一次挥手:当客户端发送数据结束后,会发送FIN码数据包给服务端,表示告知服务端客 户端的数据已经传递完了。
第二次挥手:当服务端收到FIN后,会发送ACK给客户端,表示服务端已经知道客户端传完 了。客户端收到ACK以后就会把传递数据给服务端的通道关闭;
第三次挥手:当服务端把响应的数据发送完毕后,会发送一个FIN给客户端,告知客户端响 应的数据已经发送完毕;
第四次挥手:当客户端收到FIN后,会发送一个ACK码数据包给服务端,告知服务端客户端已 经知道数据发送完毕;服务端收到ACK码后,可以安心的把数据传递通道关闭掉。


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 网关错误
**3.从浏览器输入URL按回车到页面显示都发生了什么? **

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

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主要解决三个安全问题:

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

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


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,继续使用缓存


6.ajax四步
1. 创建 XMLHttpRequest 对象,也就是创建一个异步调用对象
2. 创建一个新的 HTTP 请求,并指定该 HTTP 请求的方法、URL 及验证信息
3. 设置响应 HTTP 请求状态变化的函数
4. 发送 HTTP 请求
你使用过哪些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 选项)


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

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,则最大长度 8182byte




Cookie 和 Session 的区别?

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

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



什么是同源策略?
同源策略是客户端脚本(尤其是 Javascript)的重要的安全度量标准。其目的是防止某个文档或脚本从多个不同源装载。 这里的同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议,指一段脚本只能读取来自同一来源的窗口和文档的属性。

为什么要有同源限制?
我们举例说明:比如一个黑客程序,他利用 Iframe 把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过 Javascript 读取到你的表单中 input 中的内容,这样用户名,密码就轻松到手了


工作中是怎么解决跨域的?
1.jsonp
1) JSONP原理
利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。

2.cors
CORS 需要浏览器和后端同时支持。浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。


3.proxy代理(适用于本地开发)
。。。(其他的方式 可自行去掘金上搜 9种跨域的方式)

  - CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案
  - JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
  - 不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。
  - 日常工作中,用得比较多的跨域方案是cors和nginx反向代理


http1与http2
image.png


11.编程题

image.png


image.png


22 道高频 JavaScript 手写面试题及答案 》》

https://juejin.im/post/5e100cdef265da5d75243229


12.前端100问

https://juejin.im/post/5d23e750f265da1b855c7bbe


13.XSS和CSRF区别

  1. 跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表 CSS 混淆,故将跨站脚本攻击缩写为 XSS。恶意攻击者往 Web 页面里插入恶意 Script 代码,当用户浏览该页之时,嵌入其中 Web 里面的 Script 代码会被执行,从而达到恶意攻击用户的目的。
  2. 跨站请求伪造(Cross-site request forgery),是伪造请求,冒充用户在站内的正常操作。我们知道,绝大多数网站是通过 cookie 等方式辨识用户身份,再予以授权的。所以要伪造用户的正常操作,最好的方法是通过 XSS 或链接欺骗等途径,让用户在本机(即拥有身份 cookie 的浏览器端)发起用户所不知道的请求。

区别:

  • 原理不同,CSRF是利用网站A本身的漏洞,去请求网站A的api;XSS是向目标网站注入JS代码,然后执行JS里的代码。
  • CSRF需要用户先登录目标网站获取cookie,而XSS不需要登录
  • CSRF的目标是用户,XSS的目标是服务器
  • XSS是利用合法用户获取其信息,而CSRF是伪造成合法用户发起请求
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章