Skip to content

常见浏览器内核

  • Trident内核:IE最先开发使用,360安全浏览器兼容模式
  • Gecko内核:火狐浏览器
  • Webkit内核:Google Chrome、Safari、搜狗浏览器、360极速浏览器、阿里云浏览器登
  • Presto内核:Opera浏览器

进程与线程

进程与线程的概念

  • 进程是资源分配的最小单位 线程是程序执行的最小单位 是CPU调度的基本单位
  • 进程有自己独立的地址空间 每启动一个进程 系统都会为其分配地址空间 建立数据表来维护代码段、堆栈端和数据段 线程没有独立的地址空间 它们使用相同的地址空间共享数据
  • 创建一个线程比创建一个进程开销小 CPU切换一个线程比切换进程花费小 线程占用的资源要比进程少很多
  • 线程间通信更方便 同一个进程下 线程共享全局变量、静态变量等数据 进程之间的通信需要以通信的方式(IPC)进行 (但多线程程序处理好同步与互斥是个难点)
  • 多进程程序更安全 生命力更强 一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间) 多线程程序更不易维护 一个线程死掉 整个进程就死掉了(因为共享地址空间)
  • 进程对资源保护要求高 开销大 效率相对较低 线程资源保护要求不高 但开销小 效率高 可频繁切换
  • java内存模型(Java Memory Model,JMM)

浏览器的进程与线程

  • 浏览器是多进程的,浏览器的每一个 tab标签都代表一个独立的进程(有时候多个空白tab会合并成一个进程),浏览器内核(浏览器渲染进程)属于浏览器多进程中的一种

浏览器每个进程有多个线程,主要有以下线程:

  • GUI渲染线程:负责渲染页面,解析HTML、CSS、生成DOM树等,当页面重绘或者回流时都会调起该线程
    • GUI渲染线程和JS引擎线程是互斥的,当JS引擎线程在工作的时候,GUI渲染线程会被挂起,GUI更新会被放入JS任务队列中,等待JS引擎线程空闲的时候继续执行
  • JS引擎线程:单线程工作,负责解析运行JavaScript脚本,和GUI渲染线程互斥,JS运行耗时过长就会导致页面阻塞
  • 事件触发线程:当事件符合触发条件被触发时,该线程会把对应的事件回调函数添加到任务队列的队尾,等待JS引擎处理
  • 定时器触发线程:浏览器定时计数器并不是由JS引擎计数的,阻塞会导致计时不准确,开启定时器触发线程来计时并触发计时,计时完成后会被添加到任务队列中,等待JS引擎处理
  • http请求线程:http请求的时候会开启一条请求线程,请求完成有结果了之后,将请求的回调函数添加到任务队列中,等待JS引擎处理

DOM

DOM操作方法

获取/查找DOM节点

  • document.querySelector(selectors) // 接受一个css选择器作为参数,返回第一个匹配该选择器的元素节点
  • document.querySelectorAll(selectors) // 接受一个css选择器作为参数,返回所有匹配该选择器的元素节点
  • document.getElementsByTagName(tagName) // 返回所有指定HTML标签的元素
  • document.getElementsByClassName(className) // 返回包括了所有class名的元素
  • document.getElementsByName(name) // 用于选择含有指定name属性的HTML元素(比如form、radio、img、frame、embed、object等,或自定义属性)
  • document.getElementById(id) // 返回指定id属性的元素节点
  • document.elementFromPoint(x, y) // 返回位于页面指定位置最上层的Element子节点
  • window 元素:window
  • document 元素: document
  • html 元素: document.documentElement
  • body 元素: document.body

生成节点

  • document.createElement(tagName) // 用来生成HTML元素节点
  • document.createTextNode(text) // 用来生成文本节点
  • document.createAttribute(name) // 生成一个新的属性对象节点
  • document.createDocumentFragment() // 生成一个DocumentFragment对象(文档碎片)
    • DocumentFragment节点不属于文档树,继承的parentNode属性总是null,它有一个很实用的特点,当请求把一个DocumentFragment节点插入文档树时,插入的不是DocumentFragment自身,而是它的所有子孙节点。这个特性使得DocumentFragment成了占位符,暂时存放那些一次插入文档的节点,或把大量DOM操作放在文档碎片中进行操作。它还有利于实现文档的剪切、复制和粘贴操作。
    • 如果使用appendChid方法将原dom树中的节点添加到DocumentFragment中时,会删除原来的节点。
 var att=document.createAttribute("class");
 att.value="democlass";
 document.getElementsByTagName("H1")[0].setAttributeNode(att);

 var d=document.createDocumentFragment();
 d.appendChild(document.getElementsByTagName("LI")[0]);
 alert(d.childNodes[0].childNodes[0].nodeValue);

获取元素大小位置

  • box.style.width // "130px" 可通过parseInt获取数值
  • offsetWidth和offsetHeight(内容区域+内边距+边框)、offsetLeft和offsetTop(用于获取元素的外边框至距离最近的已定位祖先元素的内边框之间的距离)
  • clientWidth和clientHeight(内容区域+内边距 不包括滚动条 PC滚动条默认17px)、clientLeft和clientTop(获取元素的边框宽度)
  • scrollWidth和scrollHeight(用于获取包含滚动内容的元素的大小, 浏览器中实际可能与clientWidth一样)、scrollLeft和scrollTop(用于获取元素当前滚动位置,当然也可以设置元素的滚动位置。其中,scrollLeft可获取隐藏在内容区域左侧的像素数,scrollTop可获取隐藏在内容区域上方的像素数)
  • getBoundingClientRect() 可以直接获取元素与浏览器视口之前的像素数
    • 与offsetWidth获取的值一致,但是offsetWidth获取的值不收transform变换的影响,getBoundingClientRect则会
      console.log(box.getBoundingClientRect().left);   // 与视口左侧的距离
      console.log(box.getBoundingClientRect().top);   // 与视口顶部的距离
      console.log(box.getBoundingClientRect().right);   // 与视口右侧的距离
      console.log(box.getBoundingClientRect().bottom);   // 与视口底部的距离
      console.log(box.getBoundingClientRect().width);   // 内容区+内边距+边框
      console.log(box.getBoundingClientRect().height);

获取滚动距离

  • 现代浏览器:window.pageXOffset、window.pageYOffset
  • IE8及以前:document.documentElement.scrollLeft或document.body.scrollLeft,取决于文档渲染模式document.compatMode
  <!-- 获取滚动条位置 -->
  function getScrollOffsets(){
    if(window.pageXOffset!=null){
        return {
            x:window.pageXOffset,
            y:window.pageYOffset
        };
    }
    if(document.compatMode === 'CSS1Compat'){
        return {
            x:document.documentElement.scrollLeft,
            y:document.documentElement.scrollTop
        };
    }else{
        return {
            x:document.body.scrollLeft,
            y:document.body.scrollTop
        };
    }
  }
  <!-- 设置滚动条位置 -->
  function setScrollOffsets(x,y){
    document.documentElement.scrollLeft = document.body.scrollLeft = x;
    document.documentElement.scrollTop = document.body.scrollTop = y;
  }

浏览器的缓存机制

  • 1.先判断是否命中强缓存:通过Cach-Control和Expires判断是否命中强缓存,命中则从本地磁盘取对应的资源,并返回200状态码 并有from disk字样;
  • 2.再判断是否命中协商缓存:如果没有命中强缓存,则会向服务器发起请求,服务器会通过Etag和Last-Modify判断所请求的资源是否过期,即是否命中协商缓存,命中则返回304(资源未更改)状态码,并从本地取资源;
  • 3.若强缓存和协商缓存都没有命中,则服务器会返回请求结果。

http1.0和http1.1

  • Expires和Last-Modify是http1.0的内容
  • Cache-Control和Etag是http1.1的内容

缓存的优点

  • 减少了冗余的数据传输,节省宽带流量
  • 减少了服务器的负担,大大提高了网站性能
  • 加快了客户端加载网页的速度,这样正是HTTP缓存属于客户端缓存的原因

不同刷新的请求执行过程

  • 浏览器中输入url回车:依次走强缓存和协商缓存
  • F5刷新:不请求强缓存,但起码校验一下文件是否过期,即会走协商缓存
  • Ctrl + F5:强制删除浏览器中原来的缓存,去服务器请求最新的完整资源文件

浏览器的垃圾回收机制

参考 https://juejin.cn/post/6908981982017880071

  • 垃圾回收(Garbage Collection)即缩写 GC,是指一种存储器管理机制
  • 引用计数收集器:最早也最简单的垃圾回收实现方法,这种方法为占用物理空间的对象附加一个计数器,当有其他对象引用这个对象时,计数器加一,反之解除引用时减一。这种算法会定期检查尚未被回收的对象计数器,为零则会收回其所占内存空间,这种方法无法回收循环引用的存储对象。
  • 追踪收集器(标记清除算法):近现代的垃圾回收实现方法,这种算法会定期遍历它管理的内存空间,从若干根储存对象开始查找与之相关的存储对象,然后标记其余的没有关联的存储对象,最后回收这些没有关联的存储对象占用的内存空间。

JS 的类型

  • 1.弱类型,不需要声明JS引擎变量具体的数据类型,JS引擎在运行代码时会自己计算出来
  • 2.动态,意味着你可以使用同一变量保存不同类型的数据,而在用到的时候JS会自动对其进行数据类型的转换

内存空间

堆空间和栈空间

  • 在JS执行过程中,主要有三种类型的内存空间:代码空间、栈空间、堆空间
  • 代码空间主要存储可执行代码
  • 栈空间即调用栈,用来存储执行上下文,包括变量环境、词法环境等
  • 堆空间:当代码执行遇到引用类型变量,JS引擎并不是直接将该对象存放到环境变量中,而是将它分配到堆空间里面,分配后该对象会有一个在“堆”中的地址,然后将该数据的地址写入为变量的值,即栈空间中
  • 对象类型是存放在堆空间的,在栈空间中只是保留了对象的引用地址,当JS需要访问该数据的时候,是通过栈中的引用地址来访问的
  • 引用类型存放在堆空间的原因:JS引擎需要用栈来维护程序执行期间上下文的状态,如果栈空间大了的话,所有的数据都存放在栈空间里,那么会影响到上下文切换的效率,进而影响到整个程序的执行效率;比如某个foo函数执行结束了,JS引擎需要离开当前的执行上下文,只需要将指针下移道上一个执行上下文的地址就可以了,foo函数执行上下文栈空间全部回收;通常情况下,栈空间都不会设置太大,主要存放一些原始类型的小数据,而引用类型的数据占用的空间都比较大,所以这一类数据会被存放在堆中,堆空间很大,能存放很多大的数据,不过缺点是分配内存和回收内存都会占用一定的时间。

闭包在内存中的实现

  • 闭包是词法作用域下的产物
  • 闭包中的外部变量会报存储到Closure(functionname)中
  • 闭包流程的分析:
      function foo() {
      var myName = "IE";
      let test1 = 1;
      const test2 = 2;
      var innerBar = {
          getName:function(){
              console.log(test1);
              return myName;
          },
          setName:function(newName){
              myName = newName;
          }
      }
      return innerBar;
    }
    var bar = foo();
    bar.setName("Chrome");
    bar.getName();
    console.log(bar.getName());
    • 1.当 JavaScript 引擎执行到 foo 函数时,首先会编译,并创建一个空执行上下文。
    • 2.在编译过程中,遇到内部函数 setName,JavaScript 引擎还要对内部函数做一次快速的词法扫描,发现该内部函数引用了 foo 函数中的 myName 变量,由于是内部函数引用了外部函数的变量,所以 JavaScript 引擎判断这是一个闭包,于是在堆空间创建换一个“closure(foo)”的对象(这是一个内部对象,JavaScript 是无法访问的),用来保存 myName 变量。
    • 3.接着继续扫描到 getName 方法时,发现该函数内部还引用变量 test1,于是 JavaScript 引擎又将 test1 添加到“closure(foo)”对象中。这时候堆中的“closure(foo)”对象中就包含了 myName 和 test1 两个变量了。
    • 4.由于 test2 并没有被内部函数引用,所以 test2 依然保存在调用栈中。
    • 5.当执行到 foo 函数时,闭包就产生了;当 foo 函数执行结束之后,返回的 getName 和 setName 方法都引用“clourse(foo)”对象,所以即使 foo 函数退出了,“clourse(foo)”依然被其内部的 getName 和 setName 方法引用。所以在下次调用 bar.setName 或者 bar.getName 时,创建的执行上下文中就包含了“clourse(foo)”
  • 总的来说,产生闭包的核心有两步:第一步是需要预扫描内部函数;第二步是把内部函数引用的外部变量保存到堆中。

垃圾回收

垃圾回收策略

  • 垃圾回收分为手动回收和自动回收两种策略。
  • C/C++语言属于手动回收策略,需要开发者手动控制内存的分配和销毁;
  • JavaScript、Java、Python等语言属于自动回收策略,产生的垃圾数据由垃圾回收期来释放,并不需要手动通过代码来释放;

调用栈中的数据是如何回收的

  • 当一个函数执行结束之后,JavaScript引擎会通过向下移动ESP来销毁函数保存在栈中的执行上下文
  • 前文提到,JavaScript 在执行过程中会将执行上下文压入执行栈中形成调用栈,同时, 有一个记录当前执行状态的指针,称为ESP,指向调用栈中正在执行的上下文 。当函数执行完成时,需要销毁其执行上下文,JavaScript 引擎会将 ESP 下移一位,这个下移操作就是销毁栈顶的执行上下文。因为指针下移,这部分内存就会成为无效内存,当另一个调用上下文压入堆栈时,这块内容会被直接覆盖掉,用来存放另外一个函数执行的上下文。

堆中的数据是如何回收的

  • 通过JS垃圾回收器回收堆中的垃圾

代际假说

  • 1.大部分对象再内存中存在的时间很短,简单来说,就是很多对象一经分配内存,很快就变得不可访问;
  • 2.不死的对象,会活得更久

V8中堆的分区

  • V8中把堆分为新生代和老生代两个区域
  • 新生代中存放的是生存时间短的对象,老生代存放的是生存时间久的对象
  • 新生代通常只支持1~8M的容量,而老生代支持的容量就大很多,V8对这两个区域分别使用两个不同的垃圾回收器
    • 副垃圾回收器,主要负责新生代的垃圾回收
    • 主垃圾回收器,主要负责老生代的垃圾回收

垃圾回收器的工作流程:标记-回收-内存整理

  • V8对新生代和老生代分别使用两个不同的垃圾回收器,但不论什么类型的垃圾回收器,它们都有一套共同的执行流程:
  • 1.第一步是标记空间中活动对象和非活动对象。所谓活动对象就是还在使用的对象,非活动对象就是可以进行垃圾回收的对象。
    • 标记的过程:
    • 标记第一步找出所有的全局变量和当前函数栈里的变量,标记为可达。
    • 标记第二步,从已标记的数据开始,进一步标记它们可访问的变量,以此类推,专业术语叫传递闭包。
  • 2.第二步是回收非活动对象所占据的内存,其实就是在所有标记完成之后,统一清理内存中所有被标记为可回收的对象。
  • 3.第三步是做内存整理,一般来说,频繁回收对象后,内存就会存在大量不连续空间,即内存碎片。当内存中出现了大量的内存碎片之后,如果需要分配较大连续内存的时候,就有可能出现内存不足的情况。所以最后一步需要整理这些内存碎片。这一步其实是可选的,因为有的垃圾回收器不会产生内存碎片,比如JavaScript的副垃圾回收器。

副垃圾回收器

  • 副垃圾回收器主要负责清除新生代区域中的垃圾
  • 新生代中采用 Scavenge 算法,即把副垃圾回收器分为对象区域和空闲区域,当对象区域快被写满时,就会执行一次垃圾清理操作。
    • 首先会对新生代中的垃圾做标记
    • 标记完成后就进行清理阶段,副垃圾回收器会把这些存活的对象复制到空闲区域,同事它还会把这些对象有序的排列起来,所以这个复制过程,也就相当于完成了内存整理,复制后的空闲区域就没有内存碎片了
    • 完成复制后,对象区域和空闲区域进行角色翻转,这样就完成了垃圾对象的回收操作。这种角色翻转的操作还能让新生代中这两块区域无限重复使用下去
    • 每次执行清理操作时,都需要将存活的对象从对象区域复制到空闲区域。但复制操作需要时间成本,如果新生代区域空间设置得太大了,那么每次清理的时间就会过久,所以为了执行效率,一般新生代的空间会被设置的比驾小。
    • 对象晋升策略:因为新生代空间不大,很容易被存货的对象填满,为了解决这个问题,JavaScript引擎采用了对象晋升策略,也就是经过两次垃圾回收依然存活的对象,会被移动到老生区中。

主垃圾回收器

  • 主垃圾回收器主要负责清除老生代区域中的垃圾
  • 老生代区域中的对象有两个特点:一是对象占用空间大,二是对象存活时间长。
  • 标记清除算法(Mark-Sweep):主垃圾回收器采用标记-清除算法进行垃圾回收:
    • 首先是标记阶段,标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素成为活动对象,没有到达的元素就可以判断为垃圾数据。
    • 接下来就是垃圾清除阶段,它和副垃圾回收器的垃圾清除过程完成不同,这个过程是清除掉标记数据的过程
    • 多次执行标记清除算法后,会产生大量不连续的内存碎片
  • 标记-整理算法(Mark-Compact)
    • 标记过程仍然与标记清除算法里是一样
    • 标记完成以后,会让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。

全停顿和增量标记

  • 全停顿:由于JavaScript是运行在主线程之上的(单线程的),一旦执行垃圾回收算法,都需要将正在执行的JavaScript脚本暂停下来,待垃圾回收完后再回复脚本执行。这种行为叫做全停顿。
  • 当堆中老生代的垃圾数据较大时,全停顿造成的影响就较大。
  • 增量标记算法(Incremental Marking):为了降低老生代的垃圾回收造成的卡顿,V8将标记过程分为一个个子标记过程,同时让垃圾回收标记和JavaScript应用逻辑交替进行,直到标记阶段完成,这个算法叫做增量标记算法(Incremental Marking)
  • 使用增量标记算法,可以把一个完整的垃圾回收任务拆分为很多小的任务,这些小的任务执行时间比较短,可以穿插在其它JavaScript任务中间执行,从而不会感受到卡顿。而后续的清理和整理也分别采用延迟清理(lazy sweeping)和增量整理(incremental compact)
  • 增量回收过程较为复杂,需要解决两个难点:暂停重启和程序执行过程中的“副作用”。可以了解一下 三色标记(https://malcolmyu.github.io/2019/07/07/Tri-Color-Marking/)

三色标记

  • 提到Go的垃圾回收时,一般会提到三色标记算法。
  • 标记清除算法存在全停顿的问题,不能异步进行 GC 操作,对于实时性要求高的系统,这种长时间挂起的标记清除算法是不可接受的。所以就需要一个算法来解决 GC 运行时程序长时间挂起的问题,即三色标记法。
  • 三色标记法:
    • 首先将对象用三种颜色表示,分别是白色、灰色和黑色。最开始所有对象都是白色的,然后把其中全局变量和函数栈里的对象置为灰色。
    • 第二步把灰色的对象全部置为黑色,然后把原先灰色对象指向的变量都置为灰色,以此类推。等发现没有对象可以被置为灰色时,所有的白色变量就一定是需要被清理的垃圾了。
  • 三色标记法最大的好处是可以异步执行,从而可以以中断时间极小或者完全没有中断来进行整个 GC
  • 三色标记多了一个白色状态来存放不确定的对象,所以可以异步地执行,当然异步执行的代价是可能会造成一些遗漏,因为哪些早先被标记为黑色的对象可能目前已经是不可达的了。所以三色标记法是一个false negative(假阴性)的算法
  • 除了异步标记的优点,三色标记法掌握了更多当前内存的信息,因此可以更精确地按需调度,而不用像标记清除那样只能定时执行。

SEO

SEO是搜索引擎优化的简称。它是一种通过优化网站、提高网站在搜索引擎排名中的位置,从而吸引更多的有价值的流量进入网站,提高网站曝光度和知名度的技术。

SEO可以分为内部优化和外部优化两种。内部优化主要包括网站结构、内容、关键词、标题、描述等方面的优化;外部优化主要包括外部链接、社交媒体、网站口碑等方面的优化。

搜索引擎工作原理

在搜索引擎网站的后台会有一个非常庞大的数据库,里面存储了海量的关键词,而每个关键词又对应着很多网址,这些网址是被称之为“搜索引擎蜘蛛”或“网络爬虫”程序从茫茫的互联网上一点一点下载收集而来的。随着各种各样网站的出现,这些勤劳的“蜘蛛”每天在互联网上爬行,从一个链接到另一个链接,下载其中的内容,进行分析提炼,找到其中的关键词,如果“蜘蛛”认为关键词在数据库中没有而对用户是有用的便存入后台的数据库中。反之,如果“蜘蛛”认为是垃圾信息或重复信息,就舍弃不要,继续爬行,寻找最新的、有用的信息保存起来提供用户搜索。当用户搜索时,就能检索出与关键字相关的网址显示给访客。

一个关键词对应多个网址,因此就出现了排序的问题,相应的当与关键词最吻合的网址就会排在前面了。在“蜘蛛”抓取网页内容,提炼关键词的这个过程中,就存在一个问题:“蜘蛛”能否看懂。如果网站内容是flash和js等,那么它是看不懂的,会犯迷糊,即使关键字再贴切也没用。相应的,如果网站内容可以被搜索引擎能识别,那么搜索引擎就会提高该网站的权重,增加对该网站的友好度。这样一个过程我们称之为SEO。

搜索引擎优化手段

创建唯一且准确的网页标题title

title标签可以告诉用户和搜索引擎该网站的主题是什么,我们应该为网站的每个网页创建一个唯一的标题,并且避免与网页内容无关或使用默认/模糊的标题。

<!-- 正确示范 -->
<title>前端搜索引擎优化的技巧</title>

<!-- 错误示范 -->
<title>我的文档</title>

title会显示在搜索结果中,用户可以从搜索结果的页面中,可以快速获知页面内容。从而吸引潜在用户,点击当前页面进行浏览。

使用合理的 meta description

可以使用meta description标签来准确概括总结网页内容,应避免内容中出现关键词堆砌、描述过长、描述过于笼统简单,如直接拷贝关键词或正文内容、或”这是一个网页“这种没有实际性意义的描述等现象。正确示范:

<meta name='description' content='本文主要介绍搜索引擎优化(SEO)的技巧,如使用title、description、keywords、语义化标签、img的alt属性等。'>

description也会显示在搜索结果中。

使用 meta keywords

关键词(keywords)是SEO中非常重要的一部分。高频命中用户搜索的单词和短语,可以让站点提升排名,带来更多目标流量。 可以使用meta keywords来提取网页重要关键字,如:

<meta name='keywords' content='SEO,搜索引擎优化,网页排名优化'>

但有些建站者为了网页能有较好的排名,故意在这个标签中大量堆砌关键字,也就是所谓的“黑帽方法”之一。于是搜索引擎为了为用户提供优质的搜索结果,优化了它们的爬取算法——当出现大量关键字堆砌时,搜索引擎可能会降低这个网站的排名甚至将其列入黑名单。所以我们需慎用或者不用这个标签,使用的话一般设置3-4个关键词即可。

语义化书写HTML代码,符合W3C标准

尽量让代码语义化,在适当位置使用适当标签,用正确的 标签做正确的事。比如:h1-h6是用于标题类的,列表形式代码使用ul或ol,重要的文字使用strong,使用html5新增的语义化标签header、article、main、footer、aside等。 正文标题要用h1标签,h1标签自带权重,蜘蛛会认为它很重要,一个页面有且最多只能有一个h1标签,放在该页面最重要的标题上面,比如首页logo上可以加h1标签,副标题用h2标签,而其他地方不应该随便乱用h标题标签。 img标签使用alt属性加以说明,当网速很慢或图片地址失效时,就可以体现出alt属性的作用,它可以让用户在图片没有显示的时候知道这个图片的作用。 表格应使用caption表格标题标签,caption标签必须紧随table标签之后,且只能对每个表格定义一个。 br标签用于文本内容的换行。 strong、em标签,在需要强调时使用,strong标签在搜索引擎中能够得到高度的重视,能突出关键词;em标签的强调效果仅次于strong标签;b、i标签只是用于显示效果时使用,在SEO中不会起任何效果。 文本缩进不要使用特殊符号,应使用CSS进行设置。版权符号不要使用特殊符号,可以直接使用输入法打出版权符号。 重要内容不要用JS输出,因为蜘蛛不会读取js中的内容,所以重要内容必须放在html里。 尽量少用iframe框架,因为蜘蛛一般不会读取其中的内容。 谨慎只用display: none; 对于不想显示的文字内容,可以设置z-index或缩进设置足够大的负数使其偏离出浏览器之外。因为搜索引擎会过滤掉display:none其中的内容。

控制首页链接数量

网站首页是权重最高的地方,如果首页链接太少,没有”桥“,蜘蛛不能继续往下爬到内页,直接影响网站收录数量。但是首页链接也不能太多,一旦太多,没有实质性的链接,很容易影响用户体验,也会降低网站首页权重,收录效果不好。

扁平化的目录层次

尽量让蜘蛛只要跳转三次,就能到达网站内的任何一个内页。

导航优化

导航尽量采用文字方式,也可以搭配图片导航,但图片代码一定要进行优化,加上alt和title属性,告诉搜索引擎导航的定位,做到即使图片未能正常显示,用户也能看到提示文字。其次,每一个网页上应该加上面包屑导航。好处:用户体验方面,可以让用户了解当前所处位置及当前页面在整个网站中的位置,帮助用户很快了解网站组织形式;对于蜘蛛而言,能够清楚的了解网站结构,同时还增加了大量的内部链接,方便抓取,降低跳出率。

网站的结构布局:

  • 页面头部:logo及主导航,以及用户信息
  • 页面主体:左边放正文,包括面包屑导航及正文;右边放热门文章及相关文章,好处:留住访客,让访客多停留,对于蜘蛛而言,这些文章属于相关链接,增强了页面相关性,也能增强页面的权重。
  • 页面底部:版权信息和友情链接。

利用布局,把重要内容HTML代码放在最前

搜索引擎抓取HTML内容是从上到下,利用这一点,可以让主要代码优先被抓取,广告等不重要代码放在下面。 比如左右两栏布局,可以改变float样式,方便的把重要内容放在前面,让爬虫最先抓取。

控制页面大小,减少http请求,提高页面加载速度

从2010年起,谷歌一直把页面速度作为排名因素(将网速列为网页搜索排名因素)。 一个页面最好不要超过100k,太大,页面加载速度慢,当速度很慢时,用户体验不好,留不住访客,并且一旦超时,蜘蛛也会离开。 可以使用PageSpeed Insights 对站点进行全方位的体检,以满足搜索引擎的要求。

使用HTTPS

谷歌曾发公告称,使用安全加密协议(https),是搜索引擎排名的一项参考因素。 一个网站,如果不设置https,基本可以判断该网站的流量全部来自搜索引擎。 另外,还有以下好处:

  • 有利于保护搜索快照,增加快照被篡改的难度;
  • 增加被仿站镜像的难度;
  • 增加被抄袭文章内容的难度;
  • 防止被轻易篡改页面内容;
  • 用户体验更好,不会被提示为不安全网站。

使用简明扼要的URL

使用精确的目标关键词作为URL是一个不错的方法。 这样,对用户体验友好,增加点击率。

加入内链

内链指的是:从你的网站的一个页面指向另外一个页面。 一般来说,页面从内部和外部,获得的链接越多,则页面的搜索排名,会更高。 内链添加”title“属性加以说明。 这也可以说明,很多博客类网站,会在外链设置rel="nofollow"的原因:告诉搜索引擎不要追踪外链网站,避免蜘蛛爬走,页面的权重被外链分散。

获取更多的外链

外链是谷歌算法的基础,并且是最重要的排名要素之一。 谷歌认为,其他知名的网站都链接到该页面,则表明该页面的内容是高质量的。 外链的好处:

  • 提升网站的权重
  • 增加网站的信任度
  • 吸引爬虫抓取网站
  • 提升网站页面收录情况
  • 提升关键词排名
  • 给网站带来流量 这是为什么很多站长,经常要互加友链的原因。

状态码

  • 由三位数组成,第一个数字定义了响应的类别,有三种可能的值:
    • 1xx:指示信息 -- 表示请求已接收,继续处理
    • 2xx:成功 -- 表示请求已被成功接收、理解、接受
    • 3xx:重定向 -- 要完成请求必须进行进一步的操作
    • 4xx:客户端错误 -- 请求有语法错误或请求无法实现
    • 5xx:服务端错误 -- 服务器未能实现合法的请求

常见状态码

  • 200 成功:请求成功,通常服务器提供了需要的资源
  • 204 无内容: 服务器成功处理了请求,但是没有返回任何内容
  • 301 永久重定向:请求的网页已永久移动到新位置。服务器返回此响应时,会自动将请求者转到新位置
  • 302:临时重定向:服务器目前从不同位置的网页响应请求,单请求者应继续使用原有位置进行以后的请求
  • 304 未修改:从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容,浏览器从本地缓存取出请求的内容
  • 400 错误请求:服务器不理解请求的语法
  • 401 未授权:请求要求身份验证。对于需要登录的网页,服务器可能返回此响应
  • 403 禁止访问:服务器拒绝请求
    • 可能的原因:
      • ip被列入黑名单
      • 一定时间内多次访问,被防火墙拒绝
      • 连接用户过多,可稍后再试
      • 服务器繁忙,统一IP地址发送请求过多,遭到服务器只能屏蔽
    • 解决办法:
      • 重建dns缓存
      • 修改文件夹安全属性
      • 设置apache配置文件
  • 404 未找到资源:服务器找不到请求的网页
  • 422 无法处理:请求格式正确,但是由于含有语义错误,无法响应
  • 500 服务器内部错误:服务器遇到错误,无法完成请求

经典面试题:从输入URL到浏览器显示页面的过程中,都发生了什么

参考 (https://juejin.cn/post/6905931622374342670)(https://juejin.cn/post/6844903576100143117)

  • 浏览器中输入网址
    • http或https
    • url的组成
  • 通过DNS解析域名的实际IP地址
    • DNS解析先找缓存:浏览器 -> OS系统 -> 路由器 -> ISP
  • 检查浏览器是否有缓存
    • 强缓存
    • 协商缓存
  • 与WEB服务器建立TCP连接
    • 三次握手
      • 客户端通过SYN报文段发送连接请求,确定服务器是否开启端口准备连接,状态设置为SYN_SEND --- 客户端:你能接收我的消息吗?
      • 服务器如果有开着的端口并且决定接受连接,就会返回一个SYN+ACK报文段给客户端,状态设置为 SYN_RECV --- 服务端:可以的,那你能接收到我的回复吗?
      • 客户端收到服务器的SYN+ACK报文段,向服务器发送ACK报文段表示确认。此时客户端和服务端都设置为ESTABLISHED状态,连接建立,可以开始数据传输了 --- 客户端:可以的,那我们开始聊正事儿吧。
    • keep-alive:浏览器开启keep-alive后 可以建立长链接 不必每次都进行三次握手
  • 若协议是https则会做加密
  • 浏览器发送http get请求获取页面html
  • 服务器响应html
    • 这里的服务器可能是server或cdn
  • 浏览器解析HTML
    • 浏览器下载HTML数据,将html文档解析成一个个标签,这些标签组成树状结构
    • 如果解析到style标签则开始解析css,如果解析到link标签则先异步下载,完成后解析css
    • 如果遇到script标签,判断是行内写法则直接解析执行,如果是src引入则同步下载脚本文件,下载完成后立即执行;这里的下载过程是阻塞的,其他流程都会等下载完成后执行
  • 浏览器渲染页面
    • 生成 DOM树:一个深度遍历的过程,当前节点的所有节点都构建好以后才会去构建下一个兄弟节点
    • 生成 CSS树:将css解析成css规则树
    • 合并成 Render树:确定网页中都有哪些节点,各个节点的css定义以及从属关系(Render Tree渲染树并不等同于DOM树,因为一些像Header或display:none的东西就没必要放在渲染树中)
    • 页面布局:计算每个节点在屏幕中的位置
    • 页面绘制:遍历RenderTree,并使用用户界面后端层绘制每个节点。根据计算好的信息绘制整个页面。
    • 上述过程是逐步完成的,但为了更好地用户体验,渲染引擎会尽可能早的将内容呈现在屏幕上,即:解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容
  • 浏览器解析执行js脚本
    • 可能会有dom操作、ajax发起的http网络请求等
  • 浏览器发起网络请求
    • web-socket、ajax等,这个过程通常是为了获取数据
  • 服务器响应ajax请求
    • ajax请求在到达真正的server之前,可能还会经过网关权限校验、消息队列或nginx等负载均衡处理
    • 到达server后,后端会解析http请求报文,得到url、请求参数、http头、cookie等信息
    • 登录校验、权限校验(cookie校验、jwt权限校验等)
    • 可能会查询数据库,进行畅通的CRUD(增删改查)等操作
    • 返回响应数据
  • 浏览器处理事件循环等异步逻辑
    • setTimeout、setInterval、Promise等宏任务、微任务队列