阅读 577
百度面经|行经百度,方知百度

百度面经|行经百度,方知百度

前言

二走百度,感慨万分啊!文章的标题行经百度,方知百度,通过两首古诗中的诗句得来的行经百度水,祇是一漳河。东观成书日,方知百度新。标题出现两次百度正好体现了我两次面百度的经历!不扯虾犊子了,下面为大家介绍我的面试题吧!

image.png

面试题

HTML篇

html5有哪些新特性?

新增功能:HTML5 现在已经不是 SGML 的子集,主要是关于图像,位置,存储,多任务等功能的增加

  • 新增选择器 document.querySelector、document.querySelectorAll
  • 拖拽释放(Drag and drop) API
  • 媒体播放的 video 和 audio
  • 本地存储 localStorage 和 sessionStorage
  • 离线应用 manifest
  • 桌面通知 Notifications
  • 语意化标签 article、footer、header、nav、section
  • 增强表单控件 calendar、date、time、email、url、search
  • 地理位置 Geolocation
  • 多任务 webworker
  • 全双工通信协议 websocket
  • 历史管理 history
  • 跨域资源共享(CORS) Access-Control-Allow-Origin
  • 页面可见性改变事件 visibilitychange
  • 跨窗口通信 PostMessage
  • Form Data 对象
  • 绘画 canvas

CSS篇

水平垂直居中

这里我回答了三点,面试官在我说到利用绝对定位( position: relative)的时候,说让我细说一下。我并没有区分是否有定宽高和不定宽高的情况,只是说了 绝对定位 + transformflex布局绝对定位 + left/right/bottom/top + margin

定宽高

  1. 绝对定位 + transform
  2. 绝对定位和负magin值
  3. 绝对定位 + left/right/bottom/top + margin
  4. flex布局
  5. grid布局
  6. table-cell + vertical-align + inline-block/margin: auto

不定宽高

  1. 绝对定位 + transform
  2. table-cell
  3. flex布局
  4. flex变异布局
  5. grid + flex布局
  6. gird + margin布局
  7. writing-mode属性布局

当说到flex的时候,面试官问了flex:1是什么?分别解释一下这三个属性。我在之前文章解释过flex的三个属性值,flex:1 => flex: 1 1 0%。上面的具体实现方法大家可以点击下面链接去看水平垂直居中得具体实现代码。 flex是面试的必考题,大家可以多去了解一下!

flex容器属性:

display: flex

flex-direction:主轴的方向(即项目的排列方向),row | row-reverse | column | column-reverse;

flex-wrap:是否换行,nowrap | wrap | wrap-reverse;

flex-flow:direction 和 wrap简写

justify-content:主轴对齐方式,flex-start | flex-end | center | space-between | space-around;

align-items:交叉轴对齐方式,flex-start | flex-end | center | baseline | stretch;

align-content: 多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。flex-start | flex-end | center | space-between | space-around | stretch;

flex项目属性

order:项目的排列顺序,数值越小,排列越靠前,默认为0。

flex-grow:放大比例,默认为0,指定元素分到的剩余空间的比例。

flex-shrink:缩小比例,默认为1,指定元素分到的缩减空间的比例。

flex-basis:分配多余空间之前,项目占据的主轴空间,默认值为auto

flex:grow, shrink 和 basis的简写,默认值为0 1 auto

align-self:单个项目不一样的对齐方式,默认值为auto,auto | flex-start | flex-end | center | baseline | stretch;

文章链接面试官:你能实现多少种水平垂直居中的布局(定宽高和不定宽高)

flex三个属性文章链接更新|滴滴面试尴尬+愉快之旅

position

这里涉及到Position有哪些属性,哪些属性会脱离文档流? fixed 是相对于什么进行定位的呢?

描述是否脱离文档流
static默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。正常文档流
relative相对 只改变自身位置不脱离文档流
absolute生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。脱离文档流
fixed生成固定定位的元素,相对于浏览器窗口进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。脱离文档流
sticky粘性定位,该定位基于用户滚动的位置。它的行为就像 position:relative; 而当页面滚动超出目标区域时,它的表现就像 position:fixed;,它会固定在目标位置。注意: Internet Explorer, Edge 15 及更早 IE 版本不支持 sticky 定位。 Safari 需要使用 -webkit- prefix (查看以下实例)。

补充知识:脱离文档流,也就是将元素从普通的布局排版中拿走,其他盒子在定位的时候,会把脱离文档流的元素当做不存在而进行定位。需要注意的是,使用float脱离文档流时,其他盒子会无视这个元素,但其他盒子内的文本依然会为这个元素让出位置,环绕在周围。而对于使用absolute、fixed 脱离文档流的元素,其他盒子与其他盒子内的文本都会无视它。

文章链接:css脱离文档流到底是什么意思

box-sizing

box-sizing的属性有哪些,描述这些属性,这道题是类似自己写一个盒子模型(IE盒子模型)。盒子宽度100px包括margin吗?范围内包括什么?

box-sizing: content-box 是W3C盒子模型

box-sizing: border-box 是IE盒子模型

<style>
         .box1{
                width100px;
              border:1px solid black;
              box-sizing: border-box;
              padding20px;

         }
    </style>
</head>
<body>

    <div class="box1">
    </div>

</body>
复制代码

没有添加box-sizing: border-box;

EA`0@9WZMRJ2A2K91[PA]1N.png

image.png

添加了box-sizing: border-box;

image.png

image.png

补充:

盒子模型 两者区别:对于IE浏览器来说,容器的paddingborder被计算为容器的宽高,后者相反

IE盒模型 image-20210422150654259.png

标准W3C盒模型 image-20210422150745089.png

块级元素、行内元素(内联元素)和行块级元素

image.png

块级元素

特点:

  • 1.每个块级元素都是独自占一行,其后的元素也只能另起一行,并不能两个元素共用一行。

  • 2.元素的高度、宽度、行高和顶底边距都是可以设置的。  

  • 3.元素的宽度如果不设置的话,默认为父元素的宽度。

  • 常见的块级元素:<div>、<p>、<h1>...<h6>、<ol>、<ul>、<dl>、<table>、<address>、<blockquote> 、<form>

行内元素(内联元素)

特点:

  • 1.可以和其他元素处于一行,不用必须另起一行。

  • 2.元素的高度、宽度及顶部和底部边距不可设置。

  • 3.元素的宽度就是它包含的文字、图片的宽度,不可改变。

  • 常见行内元素<span>、<a>、<label>、<input>、 <img>、 <strong> 和<em>

内联块元素(块元素+内联元素)

特点:

    1. 支持全部样式
  • 2.可以自己设置宽高,如果元素没有设置宽高,则由内容决定

  • 3.盒子并在一行

    1. 代码换行,盒子产生间距
    1. 父元素可用text-align设置子元素对其方式
  • display:inline-block;将内联元素转换为内联块元素

行内元素与块级元素的转换

  • 可以在css样式中用display:inline将块级元素设为行内元素

  • 同样,也可以用display:block将行内元素设为块级元素

image.png

JS篇

浅拷贝和深拷贝

先介绍一下基本类型和引用类型,再来介绍一下浅拷贝和深拷贝。

基本类型和引用类型

基本类型有:Boolean、Null、Undefined、Number、String、Symbol

基本类型变量值存放在栈内存中,可直接访问和修改变量的值

引用类型有:Object、Array、Function

引用类型存放在堆内存中,在栈内存中变量保存的是一个指针,指向对应在堆内存中的地址。当访问引用类型的时候,要先从栈中取出该对象的地址指针,然后再从堆内存中取得所需的数据

注意:这里涉及到栈内存和堆内存的概念,面试官问我堆和栈的概念的时候,我只回答了一下栈内存,堆内存太久远了,都忘记了。

栈(stack):栈会自动分配内存空间,会自动释放,存放基本类型,简单的数据段,占据固定大小的空间

堆(heap):动态分配的内存,大小不定也不会自动释放,存放引用类型,指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量,实际上保存的不是变量本身,而是指向该对象的指针。

区别

:所有在方法中定义的变量都是放在栈内存中,随着方法的执行结束,这个方法的内存栈也自然销毁。

优点:存取速度比堆快,仅次于直接位于CPU中的寄存器,数据可以共享;

缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

:堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(参数传递)。创建对象是为了反复利用,这个对象将被保存到运行时数据区。

重点:深拷贝与浅拷贝的概念只存在于引用类型。

浅拷贝: 指两个js 对象指向同一个内存地址,其中一个改变会影响另一个;

深拷贝: 指复制后的新对象重新指向一个新的内存地址,两个对象改变互不影响。

浅拷贝实现方法

1.Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象

var a = { age: 18, name: '小度', info: { address: 'beijing', sports: 'basketball' } };
var b = Object.assign(a);
b.info.address = 'shenzhen';
b.age = 22
console.log(a); // {age: 22,name: '小度',info: { address: 'shenzhen', sports: 'basketball' } }
console.log(b); // {age: 22,name: '小度',info: { address: 'shenzhen', sports: 'basketball' } }b
复制代码
2.函数库lodash的_.clone方法

该函数库也有提供_.clone用来做 Shallow Copy,后面我们会再介绍利用这个库实现深拷贝。

var  newClone= require('lodash');
var obj = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj1 = newClone.clone(obj1);
console.log(obj.b.f === obj1.b.f);// true

复制代码
3.展开运算符...

展开运算符是一个 es6 / es2015特性,它提供了一种非常方便的方式来执行浅拷贝,这与 Object.assign ()的功能相同。

let obj1 = { name: '小度', address:{x:'beijing',y:'wuhan'}}
let obj2= {... obj1}
obj1.address.x = 'shenzhen';
obj1.name = '小白'
console.log(obj1) // { name: '小白', address: { x: 'shenzhen', y: 'wuhan' } }
console.log(obj2) // { name: '小度', address: { x: 'shenzhen', y: 'wuhan' } }
复制代码
4.Array.prototype.concat()
let a = [1,2,3,4];
let b = a.concat();
a[0] = 0;
console.log(a);  // [ 0, 2, 3, 4 ]
console.log(b);  // [ 1, 2, 3, 4 ]


let a1 = [1,2,[3,4],{name:'小度'}];
let b1 = a1.concat();
a1[3].name = '小白';
a1[0] = 0;
console.log(a1); // [ 0, 2, [ 3, 4 ], { name: '小白' } ]
console.log(b1); // [ 1, 2, [ 3, 4 ], { name: '小白' } ]
复制代码
5.Array.prototype.slice()
let arr = [1, 3, {
    username: ' 小度'
    }];
let arr3 = arr.slice();
arr3[2].username = '小白'
console.log(arr); // [ 1, 3, { username: '小白' } ]

复制代码

深拷贝实现方法

1.JSON.parse(JSON.stringify())
let arr = [1, 3, {
    username: ' 小度'
}];
let arr1 = JSON.parse(JSON.stringify(arr));
arr1[2].username = '小白'; 
console.log(arr, arr1)
复制代码

image.png

2.函数库lodash的_.cloneDeep方法
var newClone = require('lodash');
var obj = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj1 = newClone.cloneDeep(obj1);
console.log(obj.b.f === obj1.b.f);// false
复制代码
3.jQuery.extend()方法

jquery 有提供一個$.extend可以用来做 Deep Copy

$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
复制代码
var $ = require('jquery');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
复制代码
4.手写方法
  • 如果是基本类型,无需继续拷贝,直接返回
  • 如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上。
function deepClone(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
        }
    }
    return newObj;
}
复制代码

如何判断数组类型

1. instanceof方法

  • instanceof运算符是用来测试一个对象是否在其原型链原型构造函数的属性。
var arr = [];
arr instanceof Array; //true
复制代码

2. isArray方法

Array.isArray([]);//true
Array.isArray(123);//false
复制代码

3. constructor方法

  • constructor属性返回对创建此对象的数组函数的引用,就是返回对象相对应的构造函数。
var arr = [];
arr.constructor == Array;//true
复制代码

4. Object.prototype.toString方法

 Object.prototype.toString.call([])==='[object Array]'//true
     
复制代码

undefined和null的区别

  • undefined 表示不存在这个值。
  • undefined :是一个表示"无"的原始值或者说表示"缺少值",就是此处应该有一个值,但是还没有定义。尝试读取时会返回 undefined
  • 例如变量被声明了,但没有赋值时,就等于 undefined
  • null 表示一个对象被定义了,值为“空值”
  • null : 是一个对象(空对象, 没有任何属性和方法)
  • 例如作为函数的参数,表示该函数的参数不是对象;
  • 在验证 null 时,一定要使用 === ,因为 == 无法分别 null 和 undefined

原型与原型链

image.png

构造函数

构造函数一般为了区别普通函数要求首字母大写,可以通过new来新建一个对象的函数。 实例:通过构造函数和new创建出来的对象,便是实例。 实例通过__proto__指向原型,通过constructor指向构造函数

//构造函数
function Person(){}
//person 就为实例
let person = new Person()
复制代码

prototype(显示原型)

  • 在js中,每个函数对象都有一个prototype属性,这个属性指向函数的原型对象。即prototype是函数的内置属性
  • 原型对象prototype有一个默认的constructor属性,用于记录实例是由哪个构造函数创建的

proto(隐式原型)

  • 每个普通对象都会有一个__proto__,这个属性会指向该对象的原型,只有函数对象才有prototype属性。 __proto__是对象的内置属性
  • __proto__是一个对象,它有两个属性:constructor、__proto__

constructor

  • 每个原型都有一个constructor属性,指向该关联的构造函数
//constructor 指向关联的构造函数
console.log(person.__proto__.constructor === Person);//true
复制代码

准则

function Person(name, age){ 
    this.name = name;
    this.age = age;
 }
 let person= new Person('小度', 20);
    //准则1:原型对象(即Person.prototype)的constructor指向构造函数本身
   console.log(Person.prototype.constructor == Person );//true
   //准则2:实例(即person)的__proto__和原型对象指向同一个地方
   console.log(person.__proto__ == Person.prototype);//true   
复制代码

什么是原型链

每个对象都可以有一个原型,这个原型还可以有它自己的原型,以此类推,形成一个原型链。

原型链的核心就是对象的__proto__的指向,当自身不存在时,就会一层层往上找,直到找到创建对象的构造函数,直至到Object时,就没有__proto__指向了

因为__proto__实质找的是prototype,所以只要找到这个链条上的构造函数的prototype即可,Object.prototype没有__proto__属性,所以Object的原型对象Object.prototype__proto__指向null,即除Object的原型对象(Object.prototype)__proto__指向null, 其他内置函数对象的原型对象(Array.prototype等)和自定义构造函数的__proto__都指向Object.prototype,因为原型对象本身是普通对象

Object.prototype.__proto__ === null // true
Array.prototype.__proto__ === Object.prototype // true
Person.prototype.__proto__ === Object.prototype // true
复制代码

例子: 原型链继承


function Person(name, age){ 
    this.name = name;
    this.age = age;
 }
 let person= new Person('小度', 20);
 Person.prototype.sex = '女';
 Person.prototype.eat = function(){
     console.log('eat');
 }
 person.sex = '男'
 person.eat();
 console.log(person);
复制代码

image.png image.png person访问sex属性和eat方法的时候,js会先在 person 的实例属性中查找,发现找不到后,就会去 Person 的原型对象上查找,最终找到了。

这就说明,我们可以通过原型链的方式,实现 person 继承 Person 的所有属性和方法。

实践理解

 console.log(String.__proto__ === Function.prototype);//true
 console.log(Object.__proto__ === Function.prototype);//true   Object本质是函数
 console.log(Function.__proto__ === Function.prototype);//true
 console.log(Number.__proto__ === Function.prototype);//true
 console.log(Array.__proto__ === Function.prototype);//true
 console.log(Boolean.__proto__ === Function.prototype);//true
 console.log(Object.prototype.__proto__ === null);//true 原型链终点 
 console.log(Object.prototype.constructor === Object);//true
 console.log(Function.prototype.__proto__ ===  Object.prototype);//true Function.prototype 本质也是普通对象
 console.log(Function.prototype.constructor === Function);//true
复制代码
const obj = {};
console.log(obj.toString()); //[object Object] 
console.log(obj.toString === Object.prototype.toString); //true
复制代码

即使o对象中不存在o.toString方法,它也不会引发错误,而是返回字符串[object Object]。当对象中不存在属性时,它将查看其原型,如果仍然不存在,则将其查找到原型的原型,依此类推,直到在原型链中找到具有相同属性的属性为止。原型链的末尾是Object.prototype

文章链接:轻松理解JS 原型原型链

箭头函数和普通函数的区别

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。

//普通函数
var getCurrentDate = function (){
  return new Date();
}

//箭头函数
const getCurrentDate = () => new Date();
复制代码

在本例中,function(){}声明和return关键字,这两个关键字分别是创建函数和返回值所需要的。在箭头函数版本中,我们只需要()括号,不需要 return 语句,因为如果我们只有一个表达式或值需要返回,箭头函数就会有一个隐式的返回

//普通函数
function greet(name) {
  return 'Hello ' + name + '!';
}

//箭头函数
const greet = (name) => `Hello ${name}`;
const greet2 = name => `Hello ${name}`;
复制代码

我们还可以在箭头函数中使用与函数表达式和函数声明相同的参数。如果我们在一个箭头函数中有一个参数,则可以省略括号。

const getArgs = () => arguments
const getArgs2 = (...rest) => rest
复制代码

箭头函数不能访问arguments对象。所以调用第一个getArgs函数会抛出一个错误。相反,我们可以使用rest参数来获得在箭头函数中传递的所有参数。

var name = '小度';
let obj = {
     name:'小哈',
     a:function(){
     o =()=>{
         console.log(this.name);
     }
     o()
    }
}
obj.a()//小哈
复制代码

箭头函数中没有this绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则this绑定最近一层非箭头函数的this,否则thisundefined。其中箭头函数o是包含在a函数中(非箭头函数),因为a()this是指向obj的,所以this.name输出的是小哈

var name = '小度'
function b(){
    var name = '小白'
    c =()=>{
        console.log(this.name)  ;
    }
    c()
}
b()//小度
复制代码

因为箭头函数c包含在b()中,一定是要箭头函数最近一层非箭头函数,因为b()this是全局的,所以输出的是小度

call、apply、bind的区别

call和apply的区别

fun.call(thisArg[, arg1[, arg2[, ...]]])

fun.apply(thisArg, [argsArray])

其实 applycall 基本类似,他们的区别只是传入的参数不同。

apply()call()第一个参数都是改变this指向(注意:在非严格模式下,传递null/undefined指向的也是window)

区别:第二个参数的区别就是apply方法的第二个参数接收的是数组(或类似数组的对象)call 方法的第二个参数接受的是若干个参数列表。

 var a ={
        name : "小度",
        fn : function (a,b) {
            console.log( a + b) 
        }
    }
var fun = a.fn;
fun.apply(a,[1,2]);//3
复制代码
 var a ={
        name : "小度",
        fn : function (a,b) {
            console.log( a + b)
        }
    }
var fun = a.fn;
fun.call(a,1,2);//3
复制代码

bind

bind不是立即执行函数,属于预先改变this和传递一些内容。bind和call的第二个参数接受的是若干个参数列表

fun.bind(thisArg[, arg1[, arg2[, ...]]])()

区别call、apply都是改变this的同时直接将函数执行,而bind需要手动执行。

 var a ={
        name : "小度",
        fn : function (a,b) {
            console.log( a + b)
        }
    }
var fun = a.fn;
fun.bind(a,1,2);//无法执行返回3,需要手动调用
fun.bind(a,1,2)(); //3
复制代码

Git篇

image.png 上面用箭头标注的是常考的,大家可以记一下。想要了解git多人协作的话,可以点击文章链接去看看面试题|git秘籍--多人协作冲突

HTTP篇

GET和POST的区别

  • 传送方式:GET通过地址栏传输, POST 通过报文传输。

  • 传送长度:GET参数有长度限制(受限于url长度),而 POST 无限制

  • 安全性:GET 的安全性较差,因为所发送的数据是 URL 的一部分。POSTGET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。

  • GET可以被缓存,POST不能被缓存

  • GETPOST还有一个重大区别,

  • GET产生一个TCP数据包:

    对于GET方式的请求,浏览器会把http headerdata一并发送出去,服务器响应200(返回数据);

  • POST产生两个TCP数据包:

    对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

image.png

跨域

什么是跨域?

跨域指的是协议(protocol ),域名(host),端口号(post)都不相同的资源之间尝试着进行交互通信,而由于受浏览器同源策略的限制,无法正常进行交互通信。

解决跨域的方法

  1. JSONP
  2. CORS
  3. document.domain
  4. ES5 postMessage
  5. websocket
  6. nginx反向代理

这里就写了简单的概念介绍,具体的大家可以去看看大佬的文章!

文章链接:深入跨域,理论和实践都不能少 🌱【回顾 Plan】

文章链接:九种跨域方式实现原理(完整版)

XSS和CSRF

XSS, 即为(Cross Site Scripting), 中文名为跨站脚本, 是发生在目标用户的浏览器层面 上的,当渲染 DOM 树的过程成发生了不在预期内执行的 JS 代码时,就发生了 XSS 攻击。 大多数 XSS 攻击的主要方式是嵌入一段远程或者第三方域上的 JS 代码。实际上是在目 标网站的作用域下执行了这段 JS 代码。

XSS 防御的总体思路是

  • cookie 设置 httpOnly
  • 转义页面上的输入内容和输出内容
  • 输入过滤,一般是用于对于输入格式的检查,过滤掉会导致脚本执行的相关内容

CSRF(Cross Site Request Forgery,跨站请求伪造),字面理解意思就是在别的站点伪造 了一个请求。专业术语来说就是在受害者访问一个网站时,其 Cookie 还没有过期的情 况下,攻击者伪造一个链接地址发送受害者并欺骗让其点击,从而形成 CSRF 攻击。

防御 CSRF 攻击主要有三种策略:

  • 验证 HTTP Referer 字段;
  • 在请求地址中添加 token 并
  • 验证;在 HTTP 头中自定义属性并验证。

这里就写了简单的概念介绍,具体的大家可以去看看大佬的文章!

文章链接:前端面试查漏补缺--(七) XSS攻击与CSRF攻击

文章链接:CSRF与XSS攻防知识点总结

浏览器篇

从浏览器地址栏输入 URL 后发生了什么?

这个知识点涉及太多了,给了三个版本的答案,大家可以对照参考一下,或者可以点击下面大佬文章链接看看。

基础版本

  • 1.的浏览器根据请求的 URL 交给 DNS 域名解析,找到真实 IP ,向服务器发起请求;
  • 2.服务器交给后台处理完成后返回数据,浏览器接收文件( HTML、JS、CSS 、图象等)
  • 3.浏览器对加载到的资源( HTML、JS、CSS 等)进行语法解析,建立相应的内部数据结构(如 HTML 的 DOM )
  • 4.载入解析到的资源文件,渲染页面,完成。

或者

  • DNS 解析
  • TCP 连接
  • 发送 HTTP 请求
  • 服务器处理请求并返回 HTTP 报文
  • 浏览器解析渲染页面
  • 连接结束

详细版

  • 1.从浏览器接收 url 到开启网络请求线程(这一部分可以展开浏览器的机制以及进程与线程之间的关系)
  • 2.开启网络线程到发出一个完整的 HTTP 请求(这一部分涉及到 dns 查询, TCP/IP 请求,五层因特网协议栈等知识)
  • 3.从服务器接收到请求到对应后台接收到请求(这一部分可能涉及到负载均衡,安全拦截以及后台内部的处理等等)
  • 4.后台和前台的 HTTP 交互(这一部分包括 HTTP 头部、响应码、报文结构、 cookie 等知识,可以提下静态资源的 cookie 优化,以及编码解码,如 gzip 压缩等)
  • 5.单独拎出来的缓存问题, HTTP 的缓存(这部分包括http缓存头部ETag , catchcontrol 等)
  • 6.浏览器接收到 HTTP 数据包后的解析流程(解析 html -词法分析然后解析成 dom 树、解析 css 生成 css 规则树、合并成 render 树,然后 layout 、 painting 渲染、复合图层的合成、 GPU 绘制、外链资源的处理、 loaded 和 DOMContentLoaded 等)
  • 7.CSS 的可视化格式模型(元素的渲染规则,如包含块,控制框, BFC , IFC 等概念)
  • 8.JS 引擎解析过程( JS 的解释阶段,预处理阶段,执行阶段生成执行上下文, VO ,作用域链、回收机制等等)
  • 9.其它(可以拓展不同的知识模块,如跨域,web安全, hybrid 模式等等内容)

详细升级版

  • 1.在浏览器地址栏输入URL

  • 2.浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤

    • 2.3.1 HTTP1.0提供Expires,值为一个绝对时间表示缓存新鲜日期
    • 2.3.2 HTTP1.1增加了Cache-Control: max-age=,值为以秒为单位的最大新鲜时间
    • 2.1 如果资源未缓存,发起新请求
    • 2.2 如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。
    • 2.3 检验新鲜通常有两个HTTP头进行控制 ExpiresCache-Control
  • 3.浏览器解析URL获取协议,主机,端口,path

  • 4.浏览器组装一个HTTP(GET)请求报文

  • 5.浏览器获取主机ip地址,过程如下:

    • 5.1 浏览器缓存
    • 5.2 本机缓存
    • 5.3 hosts 文件
    • 5.4 路由器缓存
    • 5.5 ISP DNS 缓存
    • 5.6 DNS 递归查询(可能存在负载均衡导致每次 IP 不一致)
  • 6.打开一个socket与目标IP地址,端口建立 TCP 链接,三次握手如下:

    • 6.1 客户端发送一个TCP的SYN=1,Seq=X的包到服务器端口
    • 6.2 服务器发回SYN=1,ACK=x+1,Seq=Y的相应包
    • 6.3 客户端发送ACK=Y+1,Seq=z
  • 7.TCP链接建立后发送HTTP请求

  • 8.服务器接收请求后解析,将请求转发到服务器程序,如虚拟主机使用HTTP Host头部判断请求的服务程序

  • 9.服务器检测HTTP请求头是否包含缓存验证信息,如果验证缓存新鲜,返回 304 等对应状态

  • 10.出合理程序读取完整请求并准备HTTP相应,可能需要查询数据库等操作

  • 11.服务器将相应报文通过TCP链接发送回浏览器

  • 12.浏览器接收HTTP相应,然后根据情况选择关闭TCP链接或者保留重用,关闭 TCP 链接的四次握手如下:

    • 12.1 主动方发送Fin=1,ACK=z,Seq=x报文
    • 12.2 被动方发送ACK=X+1,Seq=Y报文
    • 12.3 被动方发送Fin=1,ACK=X,Seq=Y报文
    • 12.4 主动方发送ACK=Y,Seq=x 报文
  • 13.浏览器检查相应状态码

  • 14.如果资源可缓存,进行缓存

  • 15.对相应进行解码

  • 16.根据资源类型决定如何处理

  • 17.解析HTML文档,构建DOM树,下载资源,构建CSSOM树,执行 js 脚本,这些操作每月严格的先后顺序

  • 18.构建 DOM 树:

    • 18.1 Tokenizing:根据 HTML 规范将字符流解析为标记
    • 18.2 Lexing:词法分析将标记转换为对象并定义属性和规则
    • 18.3 DOM construction:根据 HTML 标记关系将对象组成 DOM 树
  • 19.解析过程中遇到图片、样式表、js 文件,启动下载

  • 20.构建CSSOM树:

    • 20.1 Tokenizing:字符流转换为标记流
    • 20.2 Node:根据标记创建节点
    • 20.3 CSSOM:节点创建 CSSOM 树
  • 21.  根据`DOM树和CSSOM树`构建渲染树
    复制代码
    • 21.1 从DOM树的根节点遍历所有可见节点,不可见节点包括:1) script , meta 这样本身不可见的标签。2)被 css 隐藏的节点,如 display: none
    • 21.2 对每一个可见节点,找到恰当的CSSOM规则并应用
    • 21.3 发布可视节点的内容和计算样式
  • 22.js 解析如下

    • 22.1 浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loading
    • 22.2 HTML 解析器遇到没有async和defer的script时,将他们添加到文档中,然后执行行内或外部脚本。这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。这样就可以用document.write()把文本插入到输入流中。同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作 script 和他们之前的文档内容
    • 22.3 当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。异步脚本禁止使用document.write(),它们可以访问自己 script 和之前的文档元素
    • 22.4 当文档完成解析,document.readState变成interactive
    • 22.5 所有defer脚本会按照在文档出现的顺序执行,延迟脚本能访问完整文档树,禁止使用document.write()
    • 22.6 浏览器在Document对象上触发DOMContentLoaded事件
    • 22.7 此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成载入和执行,document.readState变为complete,window触发load事件
  • 23.显示页面(HTML 解析过程中会逐步显示页面)

推荐文章链接:史上最详细的经典面试题 从输入URL到看到页面发生了什么?

cookies、sessionStorage、localStorage 和 indexDB 的区别

image.png

如何设置cookies在当前时间一个小时后失效呢?

这个就把我问懵了,因为没有去了解,只能尴尬的回面试官不知道。

方法一

String time= -1 //或0或大于1
cookie.setMaxAge(time)
//0表示立即销毁cookie
//-1表示关闭浏览器销毁cookie
//大于1是设置的销毁时间,单位是秒,例如1小时后销毁,就可以设置60*60
复制代码

方法二


import Cookies form 'js-cookie'
 
let num = 1 //失效时间是1小时
let time= new Date(new Date().getTime() + num * 60 * 60 * 1000);
Cookies.set('foo', 'bar', {
    expires: time
});
复制代码

也可以这样写

var time = 1/24;
Cookies.set('foo', 'bar', {
    expires: time
});
复制代码

失效时间为12小时

var inHalfADay = 0.5;
Cookies.set('foo', 'bar', {
    expires: inHalfADay
});
复制代码

失效时间为30分钟

var in30Minutes = 1/48;
Cookies.set('foo', 'bar', {
    expires: in30Minutes
});
复制代码

链接js-cookie官网

AJAX篇

image.png

AJAX是什么?

  • Ajax是一种异步请求数据的web开发技术,对于改善用户的体验和页面性能很有帮助。简单地说,在不需要重新刷新页面的情况下,Ajax 通过异步请求加载后台数据,并在网页上呈现出来。

  • Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发送异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面。这其中最关键的一步就是从服务器获得请求数据。要清楚这个过程和原理,我们必须对 XMLHttpRequest有所了解。

  • XMLHttpRequestajax的核心机制,它是在IE5中首先引入的,是一种支持异步请求的技术。简单的说,也就是JavaScript可以及时向服务器提出请求和处理响应,而不阻塞用户。达到无刷新的效果。

AJAX的使用

ajax是自己给自己埋了一个坑,很久之前做的项目使用了一下,写在了项目介绍里。面试官就问我原生ajax实现的步骤有哪些?我欲哭无泪啊....没有具体的去了解。 大家写在简历上的前端相关知识,最好在面试前都去看看,因为有些面试官就是从简历你写的技能点去问的!

创建Ajax核心对象XMLHttpRequest(记得考虑兼容性)

let xhr = null;
if (window.`XMLHttpRequest`) {// 兼容 IE7+, Firefox, Chrome, Opera, Safari  
	xhr = new `XMLHttpRequest`();  
} else {// 兼容 IE6, IE5 
	xhr = new ActiveXObject("Microsoft.XMLHTTP");  
} 
复制代码

向服务器发送请求

xhr.open(method, url, async);  
send(string);//`POST`请求时才使用字符串参数,否则不用带参数。
复制代码
  • method:请求的类型;GETPOST
  • url:文件在服务器上的位置
  • asynctrue(异步)false(同步)

注意:POST请求一定要设置请求头的格式内容

xhr.open("`POST`", "test.html", true);  
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");  
xhr.send("fname=Henry&lname=Ford");  //`POST`请求参数放在send里面,即请求体
复制代码

服务器响应处理(区分同步跟异步两种情况)

responseText 获得字符串形式的响应数据

responseXML 获得XML 形式的响应数据同步处理

xhr.open("`GET`","info.txt",false);  
xhr.send();  
document.`GET`ElementById("myDiv").innerHTML = xhr.responseText; //获取数据直接显示在页面上
复制代码

异步处理

xhr.onreadystatechange = function() { 
	if (xhr.readyState == 4 && xhr.status == 200){    
	document.`GET`ElementById("myDiv").innerHTML = xhr.responseText;  
	}
} 
复制代码

什么是readyState?

readyStateXMLHttpRequest对象的一个属性,用来标识当前XMLHttpRequest对象处于什么状态。

readyState总共有5个状态值,分别为0~4,每个值代表了不同的含义:

0:未初始化 — 尚未调用.open()方法;

1:启动 — 已经调用.open()方法,但尚未调用.send()方法;

2:发送 — 已经调用.send()方法,但尚未接收到响应;

3:接收 — 已经接收到部分响应数据;

4:完成 — 已经接收到全部响应数据,而且已经可以在客户端使用了;

什么是status?

HTTP状态码(status)由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型:

  • 1xx(临时响应):表示临时响应并需要请求者继续执行操作的状态码。

  • 2xx(成功):表示成功处理了请求的状态码。

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

  • 4xx(请求错误):这些状态码表示请求可能出错,妨碍了服务器的处理。

  • 5xx(服务器错误):这些状态码表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错。

常见的状态码

  • 200 表示从客户端发来的请求在服务器端被正常处理了。

  • 201 表示已创建。成功请求并创建了新的资源

  • 204 表示请求处理成功,但没有资源返回。

  • 301 表示永久性重定向。该状态码表示请求的资源已被分配了新的URI,以后应使用资源现在所指的URI。

  • 302 表示临时性重定向。

  • 304 表示客户端发送附带条件的请求时(指采用GET方法的请求报文中包含if-matched,if-modified-since,if-none-match,if-range,if-unmodified-since任一个首部)服务器端允许请求访问资源,但因发生请求未满足条件的情况后,直接返回304Modified(服务器端资源未改变,可直接使用客户端未过期的缓存)

  • 307 表示临时重定向。与302类似。使用GET请求重定向

  • 400 表示请求报文中存在语法错误。当错误发生时,需修改请求的内容后再次发送请求。

  • 401 表示未授权(Unauthorized),当前请求需要用户验证

  • 403 表示对请求资源的访问被服务器拒绝了

  • 404 表示服务器上无法找到请求的资源。除此之外,也可以在服务器端拒绝请求且不想说明理由时使用。

  • 500 表示服务器端在执行请求时发生了错误。也有可能是Web应用存在的bug或某些临时的故障。

  • 503 表示服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。

原生AJAX请求有几个步骤?分别是什么?(ajax面试重点)

//创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();
//发送信息至服务器时内容编码类型
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 
//接受服务器响应数据
xhr.onreadystatechange = function () {
    if (xhr.readyState == 4 && (xhr.status == 200) { 
        // let data = xhr.responseText;  
    }
};
//规定请求的类型、URL 以及是否异步处理请求。
xhr.open('GET',url,true);
//发送请求
xhr.send(null);  

复制代码

文章链接:AJAX原理(含常见面试题)

React篇

Hooks

面试官问我了解Hooks不,Hooks有哪些?因为接触最多的就是useState,useEffect,所以就只说了这两个, 然后面试官就让我介绍这个两个的作用。

Hooks包括:useState,useEffect,useContext,useReducer等

useState

useStatereact自带的一个hook函数,它的作用是用来声明状态变量。

调用 useState 方法的时候做了什么? 它定义一个 “state 变量”。我们的变量叫 count, 但是我们可以叫他任何名字,比如 banana。这是一种在函数调用时保存变量的方式 —— useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。

useState 需要哪些参数? useState() 方法里面唯一的参数就是初始 state。不同于class 的是,我们可以按照需要使用数字或字符串对其进行赋值,而不一定是对象。在示例中,只需使用数字来记录用户点击次数,所以我们传了 0 作为变量的初始 state。(如果我们想要在state中存储两个不同的变量,只需调用 useState() 两次即可。)

useState 方法的返回值是什么? 返回值为:当前 state 以及更新 state 的函数。这就是我们写 const [count, setCount] = useState() 的原因。这与 class 里面 this.state.countthis.setState 类似,唯一区别就是你需要成对的获取它们。如果你不熟悉我们使用的语法,我们会在本章节的底部介绍它。

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0)
    return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
复制代码

我们声明了一个叫 countstate 变量,然后把它设为 0。React 会在重复渲染时记住它当前的值,并且提供最新的值给我们的函数。我们可以通过调用 setCount 来更新当前的 count

state 变量声明为一对 [something, setSomething] 也很方便,因为如果我们想使用多个 state 变量,它允许我们给不同的 state 变量取不同的名称

function ExampleWithManyStates() {
  // 声明多个 state 变量
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
复制代码

useEffect

useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

函数式编程将那些跟数据计算无关的操作,都称为 "副效应" (side effect) 。如果函数内部直接包含产生副效应的操作,就不再是纯函数了,我们称之为不纯的函数。

useEffect() 的用法

useEffect()本身是一个函数,由 React 框架提供,在函数组件内部调用即可。

举例来说,我们希望组件加载以后,网页标题(document.title)会随之改变。那么,改变网页标题这个操作,就是组件的副效应,必须通过useEffect()来实现。

import React, { useEffect } from 'react';

function Welcome(props) {
  useEffect(() => {
    document.title = '加载完成';
  });
  return <h1>Hello, {props.name}</h1>;
}
复制代码

上面例子中,useEffect()的参数是一个函数,它就是所要完成的副效应(改变网页标题)。组件加载以后,React 就会执行这个函数。

useEffect()的作用就是指定一个副效应函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,副效应函数也会执行。

useEffect 两个注意项
  • React首次渲染和之后的每次渲染都会调用一遍useEffect函数,而之前我们要用两个生命周期函数分别表示首次渲染(componentDidMonut)和更新导致的重新渲染(componentDidUpdate)
  • useEffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数时异步执行的,而componentDidMonutcomponentDidUpdate中的代码都是同步执行的。个人认为这个有好处也有坏处吧,比如我们要根据页面的大小,然后绘制当前弹出窗口的大小,如果时异步的就不好操作了。

useEffect解绑副作用

useEffect(()=>{
        console.log('useEffect=>老弟你来了!Index页面')
        return ()=>{
            console.log('老弟,你走了!Index页面')
        }
    })
复制代码

可以根据return + useEffect() 的第二个参数 来解绑副作用

useEffect() 的第二个参数

有时候,我们不希望useEffect()每次渲染都执行,这时可以使用它的第二个参数,使用一个数组指定副效应函数的依赖项,只有依赖项发生变化,才会重新渲染。

  useEffect(()=>{
        console.log(`useEffect=>You clicked ${count} times`)

        return ()=>{
            console.log('====================')
        }
    },[count])
复制代码

上面例子中,useEffect()的第二个参数是一个数组,如:[count],指定了第一个参数(副效应函数)的依赖项{count}。只有该变量发生变化时,副效应函数才会执行。

如果第二个参数是一个空数组([]),就表明副效应参数没有任何依赖项。因此,副效应函数这时只会在组件加载进入 DOM 后执行一次,后面组件重新渲染,就不会再次执行。这很合理,由于副效应不依赖任何变量,所以那些变量无论怎么变,副效应函数的执行结果都不会改变,所以运行一次就够了。

如果useEffect没有第二个参数时,那么useEffect是不停的在调用的。

useContext 让父子组件传值更简单


import React, { useState , createContext } from 'react';
//===关键代码
const CountContext = createContext()

function Example4(){
    const [ count , setCount ] = useState(0);

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>click me</button>
            {/*======关键代码 */}
            <CountContext.Provider value={count}>
            </CountContext.Provider>

        </div>
    )
}
export default Example4;
复制代码

这段代码就相当于把count变量允许跨层级实现传递和使用了(也就是实现了上下文),当父组件的count变量发生变化时,子组件也会发生变化。接下来我们就看看一个React Hooks的组件如何接收到这个变量。

useContext 接收上下文变量

已经有了上下文变量,剩下的就时如何接收了,接收这个直接使用useContext就可以,但是在使用前需要新进行引入useContext(不引入是没办法使用的)

import React, { useState , createContext , useContext } from 'react';
复制代码

引入后写一个Counter组件,只是显示上下文中的count变量代码如下:

function Counter(){//子组件
    const count = useContext(CountContext)  //一句话就可以得到count
    return (<h2>{count}</h2>)
}
复制代码

得到后就可以显示出来了,但是要记得在<CountContext.Provider>的闭合标签中,代码如下。

import React, { useState , createContext, useContext } from 'react';
//===关键代码
const CountContext = createContext()
function Counter(){//子组件
    const count = useContext(CountContext)  //一句话就可以得到count
    return (<h2>{count}</h2>)
}
function Example4(){
    const [ count , setCount ] = useState(0);

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>click me</button>
            {/*======关键代码 */}
            <CountContext.Provider value={count}> 
            <Counter />
            </CountContext.Provider>

        </div>
    )
}
export default Example4;
复制代码

useReducer

useReducer 可以增强我们的Reducer,实现类似Redux的功能

useMemo

useMemo 主要用来解决使用React hooks产生的无用渲染的性能问题

useReduceruseMemo可以观看技术胖的视频,通过视频的实际操作可以更好的去理解。

推荐链接技术胖--React Hooks 免费视频教程(共11集)

推荐文章缪宇--用 useContext + useReducer 替代 redux

文章链接阮一峰--轻松学会 React 钩子:以 useEffect() 为例

优化篇

这里就大家列一下面试涉及要考的知识点推荐的文章,因为太多了,整理的我头大了!

优化白屏

文章链接:前端黑科技:美团网页首帧优化实践

移动端适配

文章链接:面试官:你了解过移动端适配吗?

文章分类
前端
文章标签