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是僞造成合法用戶發起請求
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章