Skip to content

Latest commit

 

History

History
1182 lines (853 loc) · 52 KB

前端面试题整理.md

File metadata and controls

1182 lines (853 loc) · 52 KB

HTML

HTML5语义化

  • 用合理、正确的标签来展示
  • 好处:
    • 易于用户阅读,样式丢失的时候能让页面呈现清晰的结构。
    • 有利于SEO,搜索引擎根据标签来确定上下文和各个关键字的权重。
    • 方便其他设备解析,如盲人阅读器根据语义渲染网页
    • 有利于开发和维护,语义化更具可读性,代码更好维护,与CSS3关系更和谐。

Reflow和Repaint

  • Reflow:当涉及到DOM节点的布局属性发生变化时,就会重新计算该属性,浏览器会重新描绘相应的元素,此过程叫Reflow(回流或重排)。
  • Repaint:当影响DOM元素可见性的属性发生变化 (如 color) 时, 浏览器会重新描绘相应的元素, 此过程称为Repaint(重绘)。因此重排必然会引起重绘。
  • 引起重排和重绘的操作
    • 调整窗口大小
    • 字体大下改变
    • 样式表变动
    • 元素内容变化,尤其是输入控件
    • CSS伪类激活,在用户交互过程中发生
    • DOM操作,DOM元素增删、修改
    • width, clientWidth, scrollTop等布局宽高的计算
  • 优化建议
    • 避免逐条更改样式。建议集中修改样式,例如操作className。
    • 避免频繁操作DOM。创建一个documentFragment或div,在它上面应用所有DOM操作,最后添加到文档里。设置display:none的元素上操作,最后显示出来。
    • 避免频繁读取元素几何属性(例如scrollTop)。绝对定位具有复杂动画的元素。
    • 绝对定位使它脱离文档流,避免引起父元素及后续元素大量的回流

href和src的区别

  • href:href标识超文本V引用,用在link和a等元素上,href是引用和页面关联,是在当前元素和引用资源之间建立联系。若在文档中添加href ,浏览器会识别该文档为 CSS 文件,就会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用 link 方式加载 CSS,而不是使用 @import 方式。
  • src:src表示引用资源,替换当前元素,用在img,script,iframe上,src是页面内容不可缺少的一部分。 当浏览器解析到src ,会暂停其他资源的下载和处理(图片不会暂停其他资源下载和处理),直到将该资源加载、编译、执行完毕,图片和框架等也如此,类似于将所指向资源应用到当前内容。这也是为什么建议把 js 脚本放在底部而不是头部的原因。

浏览器的渲染过程

  1. 解析HTML文本并构建DOM tree
  2. 解析CSS样式表并构建CSSOM tree
  3. 根据DOM tree 和 CSSOM tree 构建 Render tree
  4. 根据Render tree信息进行布局处理(Layout)
  5. 对页面元素进行绘制(Painting)

从输入 URL 到页面加载完成的过程中都发生了什么?

  1. 键盘或触屏输入URL并回车确认
  2. URL解析/DNS解析查找域名IP地址
  3. 网络连接发起HTTP请求
  4. HTTP报文传输过程
  5. 服务器接收数据
  6. 服务器响应请求/MVC
  7. 服务器返回数据
  8. 客户端接收数据
  9. 浏览器加载/渲染页面
  10. 打印绘制输出

doctype有什么作用

  • doctype是一种标准通用标记语言的文档类型声明,目的是告诉标准通用标记语言解析器要使用什么样的文档类型定义(DTD)来解析文档。

    声明是用来指示web浏览器关于页面使用哪个HTML版本进行编写的指令。 声明必须是HTML文档的第一行,位于html标签之前。

    浏览器本身分为两种模式,一种是标准模式,一种是怪异模式,浏览器通过doctype来区分这两种模式,doctype在html中的作用就是触发浏览器的标准模式,如果html中省略了doctype,浏览器就会进入到Quirks模式的怪异状态,在这种模式下,有些样式会和标准模式存在差异,而html标准和dom标准值规定了标准模式下的行为,没有对怪异模式做出规定,因此不同浏览器在怪异模式下的处理也是不同的,所以一定要在html开头使用doctype。

iframe框架有那些优缺点

优点:

  • iframe能够原封不动的把嵌入的网页展现出来。
  • 如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
  • 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
  • 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。

缺点:

  • 搜索引擎的爬虫程序无法解读这种页面
  • 框架结构中出现各种滚动条
  • 使用框架结构时,保证设置正确的导航链接。
  • iframe页面会增加服务器的http请求

label标签的作用

  • 通常是写在表单内,它关联一个控件,使用 label 可以实现点击文字选取对应的控件。

    <input type="checkbox" id="test">
    <label for="test" >test</label>

关闭form自动完成功能

  • 设置 autocomplete=off

content-type

  • Content-Type,内容类型,一般是指网页中存在的Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件,这就是经常看到一些Asp网页点击的结果却是下载到的一个文件或一张图片的原因。

CSS

github

JavaScript

面试题

JS运行机制

  • 关键词: 单线程 => 同步任务和异步任务
  • 同步任务在主线程上执行,形成一个执行栈
  • 主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件
  • 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行

变量提升和函数提升

  • 函数优先。函数声明和变量声明都会被提升,但是函数会首先被提升,然后才是变量

  • 实例

    foo(); // 1
    var foo;
    function foo() { console.log( 1 );
    }
    foo = function() { console.log( 2 );
    };

    会被引擎解析诚如下代码

    function foo() { console.log( 1 );
    }
    foo(); // 1
    foo = function() { console.log( 2 );
    };
  • 注意,var foo 尽管出现在 function foo()... 的声明之前,但它是重复的声明(因此被忽 略了),因为函数声明会被提升到普通变量之前。

纯函数

  • 相同的输入永远返回相同的输出

  • 函数的返回结果只依赖于它的参数。

  • 不修改函数的输入值

  • 函数执行过程里面没有副作用。

     // 无副作用
    const a = 1
    const foo = (obj, b) => {
      return obj.x + b
    }
    const counter = { x: 1 }
    foo(counter, 2)                       // => 3
    counter.x                             // => 1
    
    // 修改一下 ,再观察(修改了外部变量,产生了副作用。)
    const a = 1
    const foo = (obj, b) => {
      obj.x = 2;
      return obj.x + b
    }
    const counter = { x: 1 }
    foo(counter, 2)                       // => 4
    counter.x                             // => 2
    ————————————————
    版权声明:本文为CSDN博主「Zebra_9」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_42497250/java/article/details/93625668

Promise原理

  • 三种状态: 等待态(Pending)、执行态(Fulfilled)、拒绝态(Pejected)

防抖和节流

  1. 防抖:在事件被触发N秒后在执行回调,如果在这N秒内又被触发,则重新计时
    • 实现思路:通过闭包保存一个标记来保存 setTimeout 返回的值,每当用户输入的时候把前一个 setTimeout clear 掉,然后又创建一个新的 setTimeout,这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数了。
function debounce(fn, interval = 300) {
    let timeout = null;
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            fn.apply(this, arguments);
        }, interval);
    };
}
function scroll(e) {console.log(e)}
window.onscroll = debounce(scroll, 500)
  1. 节流:规定一个单位时间,在这个单位时间内,只能执行一次回调,如果触发了多次,只有一次生效
    • 实现思路: 函数的节流就是通过闭包保存一个标记(canRun = true),在函数的开头判断这个标记是否为 true,如果为 true 的话就继续执行函数,否则则 return 掉,判断完标记后立即把这个标记设为 false,然后把外部传入的函数的执行包在一个 setTimeout 中,最后在 setTimeout 执行完毕后再把标记设置为 true(这里很关键),表示可以执行下一次的循环了。当 setTimeout 还未执行的时候,canRun 这个标记始终为 false,在开头的判断中被 return 掉。
function throttle(fn, interval = 300) {
    let canRun = true;
    return function () {
        if (!canRun) return;
        canRun = false;
        setTimeout(() => {
            fn.apply(this, arguments);
            canRun = true;
        }, interval);
    };
}

深拷贝和浅拷贝

  • 浅拷贝
    • 浅拷贝就是拷贝指向对象的指针,意思就是说:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间.
    • 浅拷贝只是一种简单的拷贝,让几个对象公用一个内存,然而当内存销毁的时候,指向这个内存空间的所有指针需要重新定义,不然会造成野指针错误
  • 深拷贝
    • 所谓的深拷贝指拷贝对象的具体内容,其内容地址是自助分配的,拷贝结束之后,内存中的值是完全相同的,但是内存地址是不一样的,两个对象之间相互不影响,也互不干涉.

bind、call、apply

  • 第一个参数都是改变this的上下文,改变this指向
  • bind:改变this指向,但不会马上执行,而是返回一个函数
  • call:第二个参数开始以参数列表的形式展现
  • apply: 第二个参数接受一个数组

async/await

  • async/await实际上是Generator的语法糖。顾名思义,async关键字代表后面的函数中有异步操作,await表示等待一个异步方法执行完成。

    async function func() {
      console.log('async function is running!');
      const num1 = await 200;
      console.log(`num1 is ${num1}`);
      const num2 = await num1+ 100;
      console.log(`num2 is ${num2}`);
      const num3 = await num2 + 100;
      console.log(`num3 is ${num3}`);
    }
    
    func();
    console.log('run me before await!');
    // async function is running!
    // run me before await!
    // num1 is 200
    // num2 is 300
    // num3 is 400
  • 错误捕获

    • try/catch
    • Promise.then

Array方法

  • some: 测试是否至少有一个元素可以通过被提供的函数方法。返回布尔值

  • every: 测试一个数组内的所有元素是否都能通过某个指定函数的方法。返回布尔值

  • reduce: 对数组中的每一个元素执行一个由您提供的reduce函数,将其结果汇总为单个返回值

  • flat: 按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

    • var arr1 = [1, 2, [3, 4]];
      arr1.flat(); 
      // [1, 2, 3, 4]
      
      var arr2 = [1, 2, [3, 4, [5, 6]]];
      arr2.flat();
      // [1, 2, 3, 4, [5, 6]]
      
      var arr3 = [1, 2, [3, 4, [5, 6]]];
      arr3.flat(2);
      // [1, 2, 3, 4, 5, 6]
      
      //使用 Infinity 作为深度,展开任意深度的嵌套数组
      arr3.flat(Infinity); 
      // [1, 2, 3, 4, 5, 6]

原型和原型链

  • 详解

  • 所有函数对象的 proto 都指向 Function.prototype,它是一个空函数(Empty function)

  • 所有对象的 proto 都指向其构造器的 prototype

    function Person(name) {
      this.name = name;
    }
    var p = new Person('jack')
    console.log(p.__proto__ === Person.prototype) // true
  • 实例对象的内部原型总是指向其构造器的原型对象prototype

    function Person(name) {
        this.name = name
    }
    var p = new Person('jack')
    console.log(p.__proto__ === p.constructor.prototype) // true
  • Function.prototype.proto === Object.prototype //true

  • Object.prototype.__proto__ === null,保证原型链能够正常结束

class, extends, super

  • class: 创建一个带有作用域的对象
  • extends: class可以通过extends实现继承
  • super: 表示父类的构造函数,用来新建父类的this对象
  class Point {
  }

  class ColorPoint extends Point {
    constructor(x, y, color) {
        super(x, y); // 调用父类的constructor(x, y)
        this.color = color;
    }

    toString() {
        return this.color + ' ' + super.toString(); // 调用父类的toString()
    }
  }

Cookie,LocalStorage,SessionStorage

特性 Cookie LocalStorage SessionStorage
数据的生命期 一般由服务器生成,可设置失效时间。如果在浏览器端生成Cookie,默认是关闭浏览器后失效 除非被清除,否则永久保存 仅在当前会话下有效,关闭页面或浏览器后被清除
存放数据大小 4K左右 一般为5MB
与服务器端通信 每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题 仅在客户端(即浏览器)中保存,不参与和服务器的通信
易用性 需要程序员自己封装,源生的Cookie接口不友好 源生接口可以接受,亦可再次封装来对Object和Array有更好的支持

阻塞渲染:CSS 与 JavaScript

css

  • CSS被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕。
  • 存在阻塞的 CSS 资源时,浏览器会延迟 JavaScript 的执行和 DOM 构建。
  • CSSOM 构建时,JavaScript 执行将暂停,直至 CSSOM 就绪。
<style> p { color: red; }</style>
<link rel="stylesheet" href="index.css">
  • 这样的 link 标签(无论是否 inline)会被视为阻塞渲染的资源,浏览器会优先处理这些 CSS 资源,直至 CSSOM 构建完毕。
<link href="index.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 30em) and (orientation: landscape)">
  • 第一个资源会加载并阻塞。
  • 第二个资源设置了媒体类型,会加载但不会阻塞,print 声明只在打印网页时使用。
  • 第三个资源提供了媒体查询,会在符合条件时阻塞渲染。

JavaScript

  • 当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行。

改变阻塞模式:defer 与 async

defer

<script src="app1.js" defer></script>
<script src="app2.js" defer></script>
<script src="app3.js" defer></script>
  • defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。
  • defer 不会改变 script 中代码的执行顺序,示例代码会按照 1、2、3 的顺序执行。所以,defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后。

async

script src="app.js" async></script>
<script src="ad.js" async></script>
<script src="statistics.js" async></script>
  • async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。

对象到字符串的转换步骤

  1. 如果对象有toString()方法,javascript调用它。如果返回一个原始值(primitive value如:string number boolean),将这个值转换为字符串作为结果
  2. 如果对象没有toString()方法或者返回值不是原始值,javascript寻找对象的valueOf()方法,如果存在就调用它,返回结果是原始值则转为字符串作为结果
  3. 否则,javascript不能从toString()或者valueOf()获得一个原始值,此时throws a TypeError

函数内部arguments变量有哪些特性,有哪些属性,如何将它转换为数组

  • arguments[index]分别对应函数调用时的实参,并且通过arguments修改实参时会同时修改实参
  • arguments.length为实参的个数(Function.length表示形参长度)
  • arguments.callee为当前正在执行的函数本身,使用这个属性进行递归调用时需注意this的变化
  • 转换为数组:var args = Array.prototype.slice.call(arguments, 0);

评价一下三种方法实现继承的优缺点,并改进

function Shape() {}

function Rect() {}

// 方法1 原型继承
Rect.prototype = new Shape();

// 方法2 原型链继承
Rect.prototype = Shape.prototype;

// 方法3 混合方式继承
Rect.prototype = Object.create(Shape.prototype);

Rect.prototype.area = function () {
  // do something
};

方法1:原型继承

  • 优点:正确设置原型链实现继承
  • 优点:父类实例属性得到继承,原型链查找效率提高,也能为一些属性提供合理的默认值
  • 缺点:父类实例属性为引用类型时,不恰当地修改会导致所有子类被修改
  • 缺点:创建父类实例作为子类原型时,可能无法确定构造函数需要的合理参数,这样提供的参数继承给子类没有实际意义,当子类需要这些参数时应该在构造函数中进行初始化和设置
  • 总结:继承应该是继承方法而不是属性,为子类设置父类实例属性应该是通过在子类构造函数中调用父类构造函数进行初始化

方法2:原型链继承

  • 优点:正确设置原型链实现继承
  • 缺点:父类构造函数原型与子类相同。修改子类原型添加方法会修改父类

方法3:

  • 优点:正确设置原型链且避免方法1.2中的缺点
  • 缺点:ES5方法需要注意兼容性

改进:

  • 所有三种方法应该在子类构造函数中调用父类构造函数实现实例属性初始化
function Rect() {
    Shape.call(this);
}
  • 用新创建的对象替代子类默认原型,设置Rect.prototype.constructor = Rect;保证一致性
  • 第三种方法的polyfill:
function create(obj) {
    if (Object.create) {
        return Object.create(obj);
    }

    function f() {};
    f.prototype = obj;
    return new f();
}

方法4: Class,extends继承

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);    this.color = color; // 正确
  }
}

什么是闭包,闭包的作用

  • 闭包是一个函数,能将创建的变量的值始终保持在内存中,以供本地环境使用。
  • 函数内部可以直接访问外部变量,但在函数外部无法访问函数内部变量。使用闭包的主要作用就是间接访问函数的内部数据。
function showNum() {
  var num = 12;
  function showNum2() {
    console.log(num);
  };
  return showNum2;
}
var myNum = showNum();
myNum();//12
  • 将创建的变量的值始终保持在内存中,以供本地环境使用。
function showNum() {
  var num = 12;
  function showNum2() {
    console.log(++num);
  };
  return showNum2;
}
var myNum = showNum();
myNum();//13

Event Loop

  • event loop是一个执行模型,在不同的地方有不同的实现。浏览器和NodeJS基于不同的技术实现了各自的Event Loop。

宏队列和微队列

  • 宏队列,macrotask,也叫tasks。 一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:
    • setTimeout
    • setInterval
    • setImmediate (Node独有)
    • requestAnimationFrame (浏览器独有)
    • I/O
    • UI rendering (浏览器独有)
  • 微队列,microtask,也叫jobs。 另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:
    • process.nextTick (Node独有)
    • Promise
    • Object.observe
    • MutationObserver

浏览器的Event Loop

  • 首先执行同步代码,同步代码全部执行完毕,执行微队列(microkask),直到全部执行完毕后再执行宏队列(macrokask)
  1. 执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);
  2. 全局Script代码执行完毕后,调用栈Stack会清空;
  3. 从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1;
  4. 继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到直到把microtask queue中的所有任务都执行完毕。注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列1. 的末尾,也会在这个周期被调用执行;
  5. microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空;
  6. 取出宏队列macrotask queue中位于队首的任务,放入Stack中执行;
  7. 执行完毕后,调用栈Stack为空;
  8. 重复第3-7个步骤;
  9. 重复第3-7个步骤;

箭头函数和普通函数有什么区别

  • 箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象,用call,apply,bind也不能改变this指向
  • 箭头函数不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  • 箭头函数不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 箭头函数不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
  • 箭头函数没有原型对象prototype

排序

  • sort
var arr = [1,4,-8,-3,6,12,9,8];
arr.sort((a,b) => {
      return a-b;
})
console.log(arr);
  • 冒泡排序
var arr = [1,4,-8,-3,6,12,9,8];

function bubbleSort(arr){
    for (var i = 0; i < arr.length; i++) {
        for (var j = 0; j < arr.length-i-1; j++) {
            if(arr[j] > arr[j+1]){
                var c = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = c;
            }
        }
    }
    return arr;
}    
console.log(bubbleSort(arr));

JavaScript内存机制之问——数据是如何存储的?

  • 基本数据类型用栈存储,引用数据类型用堆存储。(闭包变量是存在堆内存中的。)
  • 基本数据类型:boolean string number null undefined symbol bigint
  • 引用数据类型:object function array

JS异步编程有哪些方案?为什么会出现这些方案?

回调函数

  • 很容易产生回调地狱。代码可读性和可维护性差,每次任务可能会失败,需要在回调里面对每个任务的失败情况进行处理,增加了代码的混乱程度。

promise

  • 解决了回调地狱,增加了代码可读性
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECT = 'reject'

function MyPromise(executor) {
    let self = this
    self.value = null
    self.error = null
    self.status = PENDING
    self.onFulfilled = null; //成功的回调函数
    self.onRejected = null; //失败的回调函数
    const resolve = (value) => {
        if(self.status !== PENDING) return
        setTimeout(() => {
            self.status = FULFILLED;
            self.value = value;
            self.onFulfilled(self.value);//resolve时执行成功回调
        })
    }
    const reject = (error) => {
       if(self.status !== PENDING) return
        setTimeout(() => {
            self.status = REJECT;
            self.error = error;
            self.onRejected(self.error);//reject时执行失败回调
        })
    }
    executor(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
  if (this.status === PENDING) {
    this.onFulfilled = onFulfilled;
    this.onRejected = onRejected;
  } else if (this.status === FULFILLED) {
    //如果状态是fulfilled,直接执行成功回调,并将成功值传入
    onFulfilled(this.value)
  } else {
    //如果状态是rejected,直接执行失败回调,并将失败原因传入
    onRejected(this.error)
  }
  return this;
}

async + await

  • ES7新增,加上 async 的函数都默认返回一个 Promise 对象

  • const readFileAsync = async function () {
      const f1 = await readFilePromise('1.json')
      const f2 = await readFilePromise('2.json')
      const f3 = await readFilePromise('3.json')
      const f4 = await readFilePromise('4.json')
    }

require和import

  • require:运行时调用;使用module.exports导出

  • import:编译时调用,必须放在文件开头引入

    • export和import
    • export:用于对外输出本模块(一个文件可以理解为一个模块)变量的接口
    • import:用于在一个模块中加载export输出变量的接口
  • require/exports 和 import/export 形式不一样

    require/exports 的写法:

    const fs = require('fs')
    exports.fs = fs
    module.exports = fs
    

    import/export 的写法:

    import fs from 'fs'
    import {default as fs} from 'fs'
    import * as fs from 'fs'
    import {readFile} from 'fs'
    import {readFile as read} from 'fs'
    import fs, {readFile} from 'fs'
    
    export default fs
    export const fs
    export function readFile
    export {readFile, read}
    export * from 'fs'
    

HTTP

HTTP与HTTPS的区别

  1. HTTP 的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头
  2. HTTP 是不安全的,而 HTTPS 是安全的
  3. HTTP 标准端口是80 ,而 HTTPS 的标准端口是443
  4. 在OSI 网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层
  5. HTTP 无法加密,而HTTPS 对传输的数据进行加密
  6. HTTP无需证书,而HTTPS 需要CA机构颁发的SSL证书

什么是HTTP协议无状态协议?怎么解决HTTP协议无状态协议?

  • 无状态协议对于事务处理没有记忆能力缺少状态意味着如果后续处理需要前面的信息
    • 也就是说,当客户端一次HTTP请求完成以后,客户端再发送一次HTTP请求,HTTP并不知道当前客户端是一个”老用户“。
  • 可以使用Cookie来解决无状态的问题,Cookie就相当于一个通行证,第一次访问的时候给客户端发送一个Cookie,当客户端再次来的时候,拿着Cookie(通行证),那么服务器就知道这个是”老用户“。

常用的HTTP的方法

  • GET: 用于请求访问已经被URI(统一资源标识符)识别的资源,可以通过URL传参给服务器
  • POST:用于传输信息给服务器,主要功能与GET方法类似,但一般推荐使用POST方式。
  • PUT: 传输文件,报文主体中包含文件内容,保存到对应URI位置。
  • HEAD: 获得报文首部,与GET方法类似,只是不返回报文主体,一般用于验证URI是否有效。
  • DELETE:删除文件,与PUT方法相反,删除对应URI位置的文件。
  • OPTIONS:查询相应URI支持的HTTP方法。

HTTP请求报文与响应报文格式

  • 请求报文
    • 请求行:包含请求方法、URI、HTTP版本信息
    • 请求首部字段
    • 请求内容实体
    • 空白行:用于通知服务器,客户端已经结束了该头的发送
  • 响应报文
    • 状态行:包含HTTP版本、状态码、状态码的原因短语
    • 响应首部字段
    • 响应内容实体
    • 空白行:用于通知客户端,服务器已经结束了该头的发送
  • 常见的首部
    • 通用首部字段(请求报文与响应报文都会使用的首部字段)
      • Date:创建报文时间
      • Connection:连接的管理
      • Cache-Control:缓存的控制
      • Transfer-Encoding:报文主体的传输编码方式
    • 请求首部字段(请求报文会使用的首部字段)
      • Host:请求资源所在服务器
      • Accept:可处理的媒体类型
      • Accept-Charset:可接收的字符集
      • Accept-Encoding:可接受的内容编码
      • Accept-Language:可接受的自然语言
    • 响应首部字段(响应报文会使用的首部字段)
      • Accept-Ranges:可接受的字节范围
      • Location:令客户端重新定向到的URI
      • Server:HTTP服务器的安装信息
    • 实体首部字段(请求报文与响应报文的的实体部分使用的首部字段)
      • Allow:资源可支持的HTTP方法
      • Content-Type:实体主类的类型
      • Content-Encoding:实体主体适用的编码方式
      • Content-Language:实体主体的自然语言
      • Content-Length:实体主体的的字节数
      • Content-Range:实体主体的位置范围,一般用于发出部分请求时使用

一次完整的HTTP请求所经历的7个步骤

建立TCP连接->发送请求行->发送请求头->(到达服务器)发送状态行->发送响应头->发送响应数据->断TCP连接

  • 建立TCP连接

在HTTP工作开始之前,Web浏览器首先要通过网络与Web服务器建立连接,该连接是通过TCP来完成的,该协议与IP协议共同构建 Internet,即著名的TCP/IP协议族,因此Internet又被称作是TCP/IP网络。HTTP是比TCP更高层次的应用层协议,根据规则, 只有低层协议建立之后才能进行更高层协议的连接,因此,首先要建立TCP连接,一般TCP连接的端口号是80。

  • Web浏览器向Web服务器发送请求行

一旦建立了TCP连接,Web浏览器就会向Web服务器发送请求命令。例如:GET /sample/hello.jsp HTTP/1.1。

  • Web浏览器发送请求头
    • 浏览器发送其请求命令之后,还要以头信息的形式向Web服务器发送一些别的信息,之后浏览器发送了一空白行来通知服务器,它已经结束了该头信息的发送。
  • Web服务器应答
    • 客户机向服务器发出请求后,服务器会客户机回送应答, HTTP/1.1 200 OK ,应答的第一部分是协议的版本号和应答状态码。
  • Web服务器发送应答头
    • 正如客户端会随同请求发送关于自身的信息一样,服务器也会随同应答向用户发送关于它自己的数据及被请求的文档。
  • Web服务器向浏览器发送数据
    • Web服务器向浏览器发送头信息后,它会发送一个空白行来表示头信息的发送到此为结束,接着,它就以Content-Type应答头信息所描述的格式发送用户所请求的实际数据
  • Web服务器关闭TCP连接
    • 一般情况下,一旦Web服务器向浏览器发送了请求数据,它就要关闭TCP连接,然后如果浏览器或者服务器在其头信息加入了这行代码:

常见的HTTP相应状态码

  • 200:请求被正常处理
  • 204:请求被受理但没有资源可以返回
  • 206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源。
  • 301:永久性重定向
  • 302:临时重定向
  • 303:与302状态码有相似功能,只是它希望客户端在请求一个URI的时候,能通过GET方法重定向到另一个URI上
  • 304:发送附带条件的请求时,条件不满足时返回,与重定向无关
  • 307:临时重定向,与302类似,只是强制要求使用POST方法
  • 400:请求报文语法有误,服务器无法识别
  • 401:请求需要认证
  • 403:请求的对应资源禁止被访问
  • 404:服务器无法找到对应资源
  • 500:服务器内部错误
  • 503:服务器正忙

HTTP1.1版本新特性

  • 默认持久连接节省通信量,只要客户端服务端任意一端没有明确提出断开TCP连接,就一直保持连接,可以发送多次HTTP请求
  • 管线化,客户端可以同时发出多个HTTP请求,而不用一个个等待响应
  • 断点续传
    • 实际上就是利用HTTP消息头使用分块传输编码,将实体主体分块传输。

HTTP优化方案

  • TCP复用:TCP连接复用是将多个客户端的HTTP请求复用到一个服务器端TCP连接上,而HTTP复用则是一个客户端的多个HTTP请求通过一个TCP连接进行处理。前者是负载均衡设备的独特功能;而后者是HTTP 1.1协议所支持的新功能
  • 内容缓存:将经常用到的内容进行缓存起来,那么客户端就可以直接在内存中获取相应的数据了。
  • 压缩:将文本数据进行压缩,减少带宽
  • SSL加速(SSL Acceleration):使用SSL协议对HTTP协议进行加密,在通道内加密并加速
  • TCP缓冲:通过采用TCP缓冲技术,可以提高服务器端响应时间和处理效率,减少由于通信链路问题给服务器造成的连接负担。

浏览器 & HTTP 缓存策略

缓存策略

  • 浏览器的缓存策略是依靠 HTTP Header 来实现的,共分为两种:
    • 强缓存
    • 协商缓存

强缓存

  • 强缓存是指在缓存期间,请求不会发送到服务器,浏览器直接返回缓存结果,需要设置 Header:

    • expires

      expires: Wed, 10 Oct 2020 09:51:00 GMT
      
      • expires 是 HTTP/1.0 中用于控制网页缓存的字段,其值代表服务器返回该请求结果的缓存到期时间,也就是说,再次发起同样的请求时,如果客户端时间小于 Expires 的值,浏览器直接返回缓存结果。
      • 由于 expires 是采用客户端时间去和缓存失效时间做对比,但客户端时间是可以做修改的,如果客户端时间和服务端时间并不同步,就会导致强缓存失效,或者时效变少。
      • 所以,在 HTTP/1.1 中增加了 cache-control 头。
    • Cache-Control

      • public:所有内容都将被缓存(客户端和代理服务器都可缓存)

      • private:所有内容只有客户端可以缓存,默认为 private

      • no-cache:客户端缓存内容,但是否使用缓存需要经过协商缓存来决定

      • no-store:所有内容都不会被缓存

      • max-age=xxx:缓存内容将在 xxx 秒之后失效

  • 如果 expires 和 cache-control 同时存在时

    • 当 expires 和 cache-control 同时存在时,只有 cache-control 生效。
    • 在某些不支持 HTTP/1.1 的环境下,expires 就会发挥用处,现阶段它的存在只是为了兼容性
  • Memory Cache & Disk Cache

    • from memory cache(内存缓存)和 from disk cache(磁盘缓存)
    • 读取 memory 中的缓存资源,肯定要比读取 disk 中的更快,但是 memory 中的缓存,会随着进程的释放而释放,也就是说,一旦我们关闭 Tab 标签,memory 中的缓存也就没有了。
    • 那么哪些资源会被缓存到 memory,哪些会缓存到 disk 中呢
      • 大文件,优先缓存至 disk,小文件优先缓存至 memory
      • 当内存占用率高的情况下,优先缓存至 disk

协商缓存

  • 如果请求没有命中强缓存,或者强缓存失效后,就需要向服务器发起请求,验证资源是否有更新,这个过程叫做协商缓存。

  • 当浏览器发起请求验证资源时,如果资源没有改变,那么服务器返回 304 状态码,并且更新浏览器缓存有效期;如果资源发生改变,那么服务器返回 200 状态码,并且返回相应资源,更新浏览器缓存有效期。

  • 那么服务器如何确定资源有没有更新呢,这里就要用到以下 2 组 HTTP 头。

  • last-modified & if-modified-since

    • last-modified 表示文件的最后修改日期,由服务器添加到 Response Header 中;
    • if-modified-since 由浏览器添加到 Request Header 中,是上一次该资源的 last-modified 值。
    • 服务器收到请求后,会将 if-modified-since 和服务器上该文件的修改时间戳进行比对,如果超过了缓存时间,那么则返回最新的资源,200 状态码,如果还在缓存有效期内,则返回 304 状态码。
    • last-modified 也有它的缺点:
    • 如果服务器上的文件被打开过,及时没有修改,它的修改时间戳也会改变,就会导致 last-modified / if-modified-since 失效,服务器再次返回同样的资源
    • last-modified / if-modified-since 是以秒为单位,如果在秒以内文件发生了修改,那么根据这组 Header 头服务器会认为文件没有修改,依然命中协商缓存,返回老的资源
    • 因为以上这些问题,于是在 HTTP/1.1 出现了 etag / if-none-match。
  • etag & if-none-match

    • etag 类似于文件指纹,可以对文件内容做摘要算法,比如 md5,生成的值作为 etag 的值,由服务器添加到 Response Header 中,浏览器再次请求该资源时,会在 Request Header 中添加 if-none-match 头,值为上次 etag 的值,服务器收到请求后,会对请求资源再次做相同的摘要算法,和 if-none-match 值进行比对,如果不一样,说明资源更新了,返回 200 以及更新后的资源文件,如果相同,说明文件没有被修改,则返回 304,由浏览器返回缓存资源。
  • 总结来说,last-modified / if-modified-sice 和 etag / if-none-match,就是将服务器返回的某一个值,由浏览器在发送请求的时候带回去,服务器拿到值后和本地文件的某个属性进行判断,来决定是否返回新的资源,还是由浏览器返回缓存资源,这个过程,就叫做协商缓存。

  • 如果什么缓存策略都没有设置,那么浏览器会采用一个启发式的算法,通常会读取 Response Header 中的 date 头,减去 last-modified 值的 10% 作为缓存时间。

实际场景

  • 频繁变动的资源

    • 完全不缓存,cache-control: no-store
    • 协商缓存,cache-control: no-cache,使浏览器每次请求都会走服务器,然后配合 etag 或者 last-modified 来验证资源是否有效,这样对比完全不缓存来说,虽然无法减少 HTTP 请求到达服务器的次数,但是可以显著减少响应数据的大小
  • 文件

    • HTML 文件不设缓存
    • CSS、JS以及图片等文件资源,可以设置一个较长的缓存有效期,比如一年,cache-control: max-age=31536000,只有当 HTML 文件引入的文件名发生变化时,才会去下载最新的资源文件,否则就一直使用缓存

主流框架

React/Vue为什么要在列表组件中写key,作用是什么?

  • key是给每一个vnode(虚拟dom)的唯一id,可以依靠 key ,更准确,更的拿到oldVnode中对应的vnode节点。

  • 更准确

    因为有key的存在,避免了就地复用,a.key === b.key的对比中,避免了就地复用情况。

  • 更快

    利用key的唯一=key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度。

React

PureComponent Vs Component

  • PureComponent通过prop和state的浅比较来实现shouldComponentUpdate,某些情况下可以用PureComponent提升性能
  • PureComponent不仅会影响本身,而且会影响子组件,所以PureComponent最佳情况是展示组件
  • 若是数组和对象等引用类型,则要引用不同,才会渲染
  • 如果prop和state每次都会变,那么PureComponent的效率还不如Component,因为你知道的,进行浅比较也是需要时间
  • 若有shouldComponentUpdate,则执行它,若没有这个方法会判断是不是PureComponent,若是,进行浅比较

生命周期

  • componentWillMount**()** – 在渲染之前执行,在客户端和服务器端都会执行。

  • componentDidMount**()** – 仅在第一次渲染后在客户端执行。

  • render() - render函数是纯函数,只返回需要渲染的东西,不应该包含其它的业务逻辑,可以返回原生的DOM、React组件、Fragment、Portals、字符串和数字、Boolean和null等内容

  • componentWillReceiveProps**()** – 当从父类接收到 props 并且在调用另一个渲染器之前调用。

  • shouldComponentUpdate**()** – 根据特定条件返回 true 或 false。如果你希望更新组件,请返回true 否则返回 false。默认情况下,它返回 false。

  • componentWillUpdate**()** – 在 DOM 中进行渲染之前调用。

  • componentDidUpdate**()** – 在渲染发生后立即调用。

  • componentWillUnmount**()** – 从 DOM 卸载组件后调用。用于清理内存空间。

React 16之后有三个生命周期被废弃(但并未删除)

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

新增生命周期

  • getDerivedStateFromProps(): static getDerivedStateFromProps(nextProps, prevState)这是个静态方法,当我们接收到新的属性想去修改我们state,可以使用getDerivedStateFromProps。此方法在更新跟挂载阶段都可能会调用
  • getSnapshotBeforeUpdate(): getSnapshotBeforeUpdate(prevProps, prevState) 被调用于render之后,可以读取但无法使用DOM的时候。它使您的组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。此生命周期返回的任何值都将作为参数传递给componentDidUpdate。

diff算法理解

  • tree diff

    • 通过updateDepth对虚拟dom树进行分层级控制,对同一层级的dom节点进行比较,当发现节点不存在时,则删除该节点及其子节点
    • dom节点跨层级操作时,会删除原节点,并在目标节点下重新创建该节点
  • component diff

    • 同一类型组件按照原策略进行比较dom树
    • 不同类型组件,即使结构相似也会删除原组件创建新的组件
  • element diff

    当节点处于同一层级时,React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)。

    • INSERT_MARKUP,新的 component 类型不在老集合里, 即是全新的节点,需要对新节点执行插入操作。
    • MOVE_EXISTING,在老集合有新 component 类型,且 element 是可更新的类型,generateComponentChildren 已调用 receiveComponent,这种情况下 prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点。
    • REMOVE_NODE,老 component 类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者老 component 不在新集合里的,也需要执行删除操作。
    • 优化策略:对同一层级的同一组节点添加唯一key
    • diff算法:
      • 对新集合的节点进行循环遍历,通过唯一key来判断新老集合中是否存在相同的节点,如果存在则进行移动操作,但移动前会进行当前节点在老集合中位置与当前index的比较,若当前index比lastIndex小,则移动,否则该节点不会影响其他节点,则不动;
      • 若遍历中遇到老集合不存在但新集合存在的新节点,则创建新节点
      • 完成新集合的所有节点diff后,重新遍历老集合,检查是否存在新集合没有但老集合仍存在的节点,有就删除

[参考链接](

Git

git放弃修改,强制覆盖本地代码

git fetch --all
git reset --hard origin/master 
git pull

git 合并多个commit

  1. 查看提交历史git log

  2. git rebase

    1. 从HEAD版本开始往过去数3个版本

      git rebase -i HEAD~3
      
    2. 指名要合并的版本之前的版本号

      git rebase -i 3a4226b
      
  3. 选取要合并的提交

    1. 执行了rebase命令之后,会弹出一个窗口,头几行如下:

      pick 3ca6ec3   '注释**********'
      
      pick 1b40566   '注释*********'
      
      pick 53f244a   '注释**********'
      
    2. 将pick改为squash或者s,之后保存并关闭文本编辑窗口即可。改完之后文本内容如下:

      pick 3ca6ec3   '注释**********'
      
      s 1b40566   '注释*********'
      
      s 53f244a   '注释**********'
      
    3. 然后保存退出,Git会压缩提交历史,如果有冲突,需要修改,修改的时候要注意,保留最新的历史,不然我们的修改就丢弃了。修改以后要记得敲下面的命令:

      git add .  
      
      git rebase --continue  
      
      • 如果你想放弃这次压缩的话,执行以下命令:
      git rebase --abort  
      
  4. 解决冲突

  5. 输入wq保存并推出, 再次输入git log查看 commit 历史信息,你会发现这两个 commit 已经合并了

git merge和git rebase区别

git merge

  • 会产生一个新的commit,如果合并时遇到冲突,仅需要修改后重新commit
  • 优点:记录了真实的commit情况,包括每个分支的详情
  • 缺点:每次merge会自动产生一个merge commit,commit比较频繁时,看到分支很杂乱

git rebase

  • 本质是变基
  • 会合并之前的commit历史
  • 优点:得到更简洁的项目历史,去掉了merge commit
  • 缺点:如果合并出现代码问题不容易定位,因为re-write了history
  • 合并时如果出现冲突需要按照如下步骤解决
    • 修改冲突部分
    • git add
    • git rebase --continue
    • (如果第三步无效可以执行 git rebase --skip)

小程序

微信小程序代码量限制问题

  • 单个包代码量不大于2M
  • 所有分包大小不超过8M
  • 分包加载:通过在app.json subPackages 字段声明项目分包结构
{
    'pages': [
          'pages/welcome',
          'pages/movie',
           'pages/tvPlay'
           // 'pages/detail'
    ],
    'subPackages': [
      {
         'root': 'packageA',
            'pages': [
              'pages/detail'
        ]
      }
    ]
}

VUE

MVVM的理解

  • Model-View-ViewModel的缩写,Model代表数据模型,View代表UI组件,ViewModel将Model和View关联起来
  • 数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据

Vue2.x响应式数据/双向绑定原理

  • Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据。其中,View变化更新Data,可以通过事件监听的方式来实现,所以 Vue数据双向绑定的工作主要是如何根据Data变化更新View

简述

  • 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
  • 这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。
  • 每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

深入理解

  • 监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
  • 解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
  • 订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新——这是一个典型的观察者模式
  • 订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

Vue3.x响应式数据原理

Vue3.x改用Proxy替代Object.defineProperty

  • 因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。

  • Proxy只会代理对象的第一层,Vue3是怎样处理这个问题的呢?

    • 判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。
    • 监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。

Proxy 与 Object.defineProperty 优劣对比

  • Proxy 的优势如下:

    • Proxy 可以直接监听对象而非属性;
  • Proxy 可以直接监听数组的变化;

    • Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
    • Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
    • Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;
  • Object.defineProperty 的优势如下:

    • 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。

vue中的diff算法

原理: (patchVnode是diff发生的地方,整体策略:深度优先,同层比较)

  • 1.先同级比较,在比较子节点
  • 2.先判断一方有儿子一方没儿子的情况
  • 3.比较都有儿子的情况
  • 4.递归比较子节点

总结:

  • 1.diff算法是虚拟DOM技术的必然产物:通过新旧虚拟DOM作对比(即diff),将变化的地方更新在真实DOM上;另外,也需要diff高效的执行对比过程,从而降低时间复杂度为O(n)。
  • 2.vue 2.x中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,只有引入diff才能精确找到发生变化的地方。
  • 3.vue中diff执行的时刻是组件实例执行其更新函数时,它会比对上一次渲染结果oldVnode和新的渲染结果newVnode,此过程称为patch。
  • 4.diff过程整体遵循深度优先、同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文本节点做不同操作;比较两组子节点是算法的重点,首先假设头尾节点可能相同做4次比对尝试,如果没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点;借助key通常可以非常精确找到相同节点,因此整个patch过程非常高效

vue项目中遇到的问题

  • vantUI使用popup,父组件控制子组件显示隐藏,因为单向数据流不能修改props,通过computed控制隐藏,且computed属性要设置get,set