个人总结前端面试

这篇博客详细总结了前端面试的各个重点知识点,包括JavaScript的原型链与继承、闭包和垃圾回收机制、作用域与变量声明提升,以及var、let、const的区别。此外,还涵盖了JavaScript对象的原型、继承的五种方法、Class、数据类型等内容。同时,讨论了CSS的相关知识,如回流重绘、布局技巧和CSS动画。还提到了HTML的meta标签、语义化和SEO。博客深入探讨了网络通信协议,如HTTP的版本差异、状态码、跨域以及TCP的三次握手和四次挥手。最后,博主分享了前端开发的理解,如重构、性能优化、服务端渲染、前端代码的编写原则,以及前端新技术如微前端、Serverless、Flutter等。
摘要由CSDN通过智能技术生成

前端面试个人总结


一、JS

1.原型链与继承

每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时
如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念

  • 关系:instance.constructor.prototype = instance.__proto__
  • 特点:
    JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
    当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有的就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象
  • 原型:
    JavaScript的所有对象中都包含了一个 __proto__ 内部属性,这个属性所对应的就是该对象的原型
    JavaScript的函数对象,除了原型 __proto__ 之外,还预置了 prototype 属性
    当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型__proto__
  • 原型链
    JavaScript对象通过__proto__ 指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条, 即原型链
    当一个对象调用的属性/方法自身不存在时,就会去自己 __proto__关联的前辈 prototype 对象上去找
    如果没找到,就会去该 prototype 原型 __proto__ 关联的前辈 prototype 去找。依次类推,直到找到属性/方法或 undefined 为止。从而形成了所谓的“原型链”
  • 原型特点:
    JavaScript对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变

2.闭包与垃圾回收机制

  • 闭包就是能够读取其他函数内部变量的函数。 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域。

  • 闭包的特性: 函数内再嵌套函数 内部函数可以引用外层的参数和变量 参数和变量不会被垃圾回收机制回收

  • 说说你对闭包的理解: 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念

  • 闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是是封装对象的私有属性和私有方法,让这些变量始终保持在内存中
    好处:能够实现封装和缓存等;
    坏处:就是消耗内存、不正当使用会造成内存溢出的问题

  • 使用闭包的注意点 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露
    解决方法是,在退出函数之前,将不使用的局部变量全部删除

  • 垃圾回收机制:标记清除,引用计数
    v8的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两 个特点,一是新生的对象容易早死,另一个是不死的对象会活得更久。基于 这个假说,v8
    引擎将内存分为了新生代和老生代。

    1. 首先检查 From 空间的存活对象,如果对象存活则判断对象是否满足晋升到老生 代的条件,如果满足条件则晋升到老生代。如果不满足条件则移动 To 空间。
    2. 如果对象不存活,则释放对象的空间。
    3. 最后将 From 空间和 To 空间角色进行交换。

    新生代对象晋升到老生代有两个条件:

    1. **判断对象是否已经经过一次 Scavenge 回收。**若经历过,则将对象从 From 空间复制到老生代中;若没有经历,则复制到 To 空间。
    2. **To 空间的内存使用占比是否超过限制。**当对象从 From 空间复制到 To 空 间时,若 To 空间使用超过 25%,则对象直接晋升到老生代中。设置 25%的原因主要是因为算法结束后,两个空间结束后会交换位置,如果 To
      空间的内存太小,会影响后续的内存分配。

    增量标记的方法,将一次停顿进行的过程分为了多步,每次执行完一小步就让运行 逻辑执行一会,就这样交替运行

3.作用域与变量声明提升,var,let,const

变量声明提升:
在JavaScript中,函数声明与变量声明经常被JavaScript引擎隐式地提升到当前作用 域的顶部。
声明语句中的赋值部分并不会被提升,只有名称被提升
函数声明的优先级高于变量,如果变量名跟函数名相同且未赋值,则函数声明会覆盖变 量声明
如果函数有多个同名参数,那么最后一个参数(即使没有定义)会覆盖前面的同名参数

  • let
    允许你声明一个作用域被限制在块级中的变量、语句或者表达式
    let绑定不受变量提升的约束,这意味着let声明不会被提升到当前
    该变量处于从块开始到初始化处理的“暂存死区”

  • var
    声明变量的作用域限制在其声明位置的上下文中,而非声明变量总是全局的
    由于变量声明(以及其他声明)总是在任意代码执行之前处理的,所以在代码中的任意 位置声明变量总是等效于在代码开头声明

  • const
    声明创建一个值的只读引用 (即指针)
    基本数据当值发生改变时,那么其对应的指针也将发生改变,故造成 const申明基本数 据类型时再将其值改变时,将会造成报错
    但如果是复合类型时,如果只改变复合类型的其中某个Value项时,还是正常使用

  • 块级作用域:ES6新增,可由let声明

  • 全局作用域:最外层的作用域,如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样的坏处就是很容易撞车、冲突。

  • 函数作用域:因放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。

4.读入数据

每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时
如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念

  • 关系:instance.constructor.prototype = instance.proto
  • 特点:
    JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变
    当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有的
    就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象
  • 原型:
    JavaScript的所有对象中都包含了一个 [proto] 内部属性,这个属性所对应的就是该对象的原型
    JavaScript的函数对象,除了原型 [proto] 之外,还预置了 prototype 属性
    当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 [proto]。
  • 原型链
    当一个对象调用的属性/方法自身不存在时,就会去自己 [proto] 关联的前辈 prototype 对象上去找
    如果没找到,就会去该 prototype 原型 [proto] 关联的前辈 prototype 去找。依次类推,直到找到属性/方法或 undefined 为止。从而形成了所谓的“原型链”
    原型特点:
    JavaScript对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变

5.继承的五种方法

  • 原型链继承:将子对象的 prototype 指向父对象的 prototype。将父类的实例作为子类的原型,他的特点是实例是子类的实例也是父类的实例,父类新增的原型方法/属性,子类都能够访问,并且原型链继承简单易于实现,缺点是来自原型对象的所有属性被所有实例共享,无法实现多继承,无法向父类构造函数传参。
function extend(Child, Parent) {
    var F = function(){};
   F.prototype = Parent.prototype;
   Child.prototype = new F();
   Child.prototype.constructor = Child;
   Child.uber = Parent.prototype;
}
  • 构造继承:使用 call 或 apply 方法,将父对象的构造函数绑定在子对象上使用父类的构造函数来增强子类实例,即复制父类的实例属性给子类,构造继承可以向父类传递参数,可以实现多继承,通过call多个父类对象。但是构造继承只能继承父类的实例属性和方法,不能继承原型属性和方法,无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
function Cat(name,color){
  Animal.apply(this, arguments);
  this.name = name;
  this.color = color;
}
  • 实例继承:将子对象的 prototype 指向父对象的一个实例。为父类实例添加新特性,作为子类实例返回,实例继承的特点是不限制调用方法,不管是new 子类()还是子类()返回的对象具有相同的效果,缺点是实例是父类的实例,不是子类的实例,不支持多继承
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
  • 拷贝继承把父对象的所有属性和方法,拷贝进子对象
    特点:支持多继承,缺点:效率较低,内存占用高(因为要拷贝父类的属性)无法获取父类不可枚举的方法(不可枚举方法,不能使用for in访问到)
function extend(Child, Parent) {
   var p = Parent.prototype;
   var c = Child.prototype;
   for (var i in p) {
      c[i] = p[i];
   }
   c.uber = p;
  }
  • 组合继承:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
  • ⭐寄生组合继承:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

6.Class

本质:其实在 JS中并不存在类,class 只是语法糖,本质还是函数
用法:可以使用 class 去实现继承 class Child extends Parent

7.数据类型

  • 基本数据类型:undefined、null、boolean、number、string、symbol 基础类型存储在栈内存,被引用或拷贝时,会创建一个完全相等的变量;占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。

  • 引用数据类型:object、array、function
    引用类型存储在堆内存,存储的是地址,多个引用指向同一个地址,这里会涉及一个“共享”的概念;占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

8.typeof()判断数据类型,类型转换

typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
Typeof null // 'object'
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'

9.=====的区别

对于 == 来说,如果对比双方的类型不一样的话,就会进行类型转换

  1. 首先会判断两者类型是否相同。相同的话就是比大小了
  2. 类型不相同的话,那么就会进行类型转换
  3. 会先判断是否在对比 null 和 undefined,是的话就会返回 true
  4. 判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number
  5. 判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断
  6. 判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object转为原始类型再进行判断
    在这里插入图片描述

10.判断数组的方法

  1. arr instanceof Array
    如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。
  2. arr.constructor === Array Object的每个实例都有构造函数
    constructor,用于保存着用于创建当前对象的函数。
  3. Array.prototype.isPrototypeOf(arr) Array.prototype 属性表示 Array
    构造函数的原型。
  4. Object.getPrototypeOf(arr) === Array.prototype
    Object.getPrototypeOf() 方法返回指定对象的原型
  5. Object.prototype.toString.call(arr) === '[object Array]'
  6. Array.isArray(arr) ES5新增

11.遍历对象或数组的方法

  1. for…in方法是ES5标准,遍历的是数组或对象的key,建议用for in去遍历对象
  2. for…of方法是ES6标准,遍历的是value,建议去遍历数组
  3. forEach方法,是最基本的方法,就是遍历与循环,默认有3个传参:分别是遍历的数组内容item、数组索引index、和当前遍历数组Array
  4. map方法,基本用法与forEach一致,但是不同的,它会返回一个新的数组,所以在callback需要有return值,如果没有,会返回undefined

12.数组push和直接赋值

在使用push的时候,每次JS需要先找到当前索引的最大值,再在此基础上生成新的索引值,然后对新的索引值进行赋值。而[i]则是直接对相应的索引赋值。当操作大批量的元素的时候,直接根据下标操作,反而要比经过各种常见的迭代方法要快。

13.this和箭头函数特点

  • this
    箭头函数优先级最高
    new 绑定: 函数是否在 new 中调用?如果是的话 this 绑定的是新创建的对象;
    显式绑定: 函数是否是通过 bind、call、apply 调用?如果是的话,this 绑定的是指定的对象;
    隐式绑定: 函数是否在某个上下文对象中调用?如果是的话,this 绑定的是那个上下文对象;
    如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象;
  1. 箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this
  2. 不可以使用new命令,否则会抛出一个错误
  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替
  4. 不可以使用yield命令,因此箭头函数不能用作Generator函数

14.纯函数

函数的返回结果只依赖于它的参数。
函数执行过程里面没有副作用。

15.函数柯里化add(1)(2)(3)(4,5)()

function add(...args){
	return args.reduce((a, b) => a + b)
}
function curring(fn){
	let args = [];
	return function _c(...newArgs) {
		if(newArgs.length){
			args = [...args,...newArgs]
			return _c;
		}
		else{
			return fn.apply(this,args)
		}
	}
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)(4,5)())

16.深拷贝

function deepClone(obj){
	//如果是基本类型或者null,return
	if(typeof obj !== 'obj' || obj === null){
		return obj;
	}
	//定义结果对象
	let copy = {};
	if(obj.constructor === Array){
		copy = [];
	}
	//遍历对象的key
	for(let key in obj){
		//如果key是对象的自有属性
		if(obj.hasOwnProperty(key)){
			//递归调用深拷贝
			copy[key] = deepClone(obj[key]);
		}
	}
	return copy;
}

17.函数与对象区别使用的场景

  • 函数是对js操作过程的封装,以后操作同样的过程,只要调用相应的函数(方法)即可。
  • 对象同样是对js代码封装,不过对象可以封装函数(方法)。比如把某一类的函数(方法)都封装到某个对象中。这样可以系统的管理调用函数(方法)

18.async,await,promise

异步:JS是单线程语言,在执行一些比较耗时的操作(比如常见的Ajax请求)时,为了不阻塞后面代码的执行,往往需要执行异步操作。

  • Promise
    Promise 有四种状态:
    pending: 初始状态, 非 fulfilled 或 rejected.
    fulfilled: 成功的操作.
    rejected: 失败的操作.
    settled: Promise已被fulfilled或rejected,且不是pending
    fulfilled 与 rejected 一起合称 settled
    Promise 对象用来进行延迟(deferred) 和异步(asynchronous ) 计算
    可以把 Promise看成一个状态机。初始是 pending 状态,可以通过函数 resolve 和 reject,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化。
    then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then调用就失去意义了

await后面接一个会return new promise的函数并执行它。
await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把await 命令放在 try…catch 代码块中。
await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。
await只能放在async函数里,async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。
Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它

19.Event loop

  1. 首先js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈 中来保证代码的有序执行
  2. 在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是 会将这个事件挂起,继续执行执行栈中的其他任务
  3. 当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任 务队列中等待执行
  4. 任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js
    引擎 首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中 执行
  5. 当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。
  • 微任务
    process.nextTick
    promise
    Object.observe
    MutationObserver
  • 宏任务
    script
    setTimeout
    setInterval
    setImmediate
    事件绑定
    I/O 网络请求完成、文件读写完成事件
    UI rendering
    用户交互事件(比如鼠标点击、滚动页面、放大缩小等)
  • requestAnimationFrame不属于宏任务也不属于微任务,因为它是独立于主线程之外的任务,不归主线程管。

20.DOM(js事件机制/事件流)

DOM事件流:浏览器在于当前页面做交互时,这个事件是怎么传递到页面上的。
完整的事件流三个阶段:

  1. 捕获:从 window 对象传到 目标元素。事件依次传递的顺序是:window --> document --> html --> body --> 父元素、子元素、目标元素。
  2. 目标阶段:事件通过捕获,到达目标元素,这个阶段就是目标阶段。
  3. 冒泡:从目标元素传到 Window 对象。
  • event对象方法
  1. event.preventDefault():阻止默认事件。
  2. event.stopPropagation():阻止冒泡。
  3. event.stopImmediatePropagation():设置事件优先级。
  4. event.currentTarget 当前所绑定的事件对象。在事件委托中,指的是【父元素】。
  5. event.target 当前被点击的元素。在事件委托中,指的是【子元素】。
  • 事件
    注册事件:
    在DOM元素中直接绑定;
    在JavaScript代码中绑定;
    绑定事件监听函数。
    自定义事件
var ev = new Event("look", {"bubbles":true, "cancelable":false, "composed":false});
document.dispatchEvent(ev);
//"bubbles":可选,Boolean类型,默认值为 false,表示该事件是否冒泡。
//"cancelable":可选,Boolean类型,默认值为 false, 表示该事件能否被取消。
//"composed":可选,Boolean类型,默认值为 false,指示事件是否会在影子DOM根节点之外触发侦听器。

遍历方式和相关属性

21.JS操作DOM

  • 创建新节点
    createDocumentFragment() //创建一个DOM片段
    createElement() //创建一个具体的元素
    createTextNode() //创建一个文本节点

  • 添加、移除、替换、插入
    appendChild() //添加
    removeChild() //移除
    replaceChild() //替换
    insertBefore() //插入

  • 查找
    getElementsByTagName() //通过标签名称
    getElementsByName() //通过元素的Name属性的值
    getElementById() //通过元素Id,唯一性

dom相关取值

  1. 偏移量
    offsetHeight/offsetWidth: 表述元素的外尺寸:元素内容+内边距+边框(不包括外边距)
    offsetLeft/offsetTop: 表示该元素的左上角(边框外边缘)与已定位的父容器(offsetParent对象)左上角的距离。
    offsetParent元素是指元素最近的定位(relative,absolute)祖先元素,可递归上溯。
  2. 客户区大小
    clientWidth/clientHeight: 用于描述元素的内尺寸:元素内容+两边内边距
    innerWidth/innerHeight: 常用于获取视口的高宽
  3. 滚动大小(scrollHeight scrollWidth scrollLeft scrollTop)
    scrollHeight/scrollWidth: 元素内容的总高度或宽度
    scrollLeft/scrollTop:是指元素滚动条位置,它们是可写的(被隐藏的内容区域左侧/上方的像素)
    浏览器窗口的滚动条位置:window对象的pageXoffset和pageYoffset, IE 8及更早版本可以通过scrollLeft和scrollTop属性获得滚动条位置
  • .getBoundingClientRect()
    返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合,就是该元素的 CSS 边框大小。返回的结果是包含完整元素的最小矩形,并且拥有left, top, right, bottom, x, y, width, 和 height这几个以像素为单位的只读属性用于描述整个边框。除了width 和 height 以外的属性是相对于视图窗口的左上角来计算的。
var el = document.getElementById('list-content'); //获取元素
function getElemDis(el) {
    var tp = document.documentElement.clientTop,
        lt = document.documentElement.clientLeft,
        rect = el.getBoundingClientRect();
    return {
        top: rect.top - tp,
        right: rect.right - lt,
        bottom: rect.bottom - tp,
        left: rect.left - lt
    }
}

22.虚拟DOM

Virtual DOM(虚拟 DOM),是由普通的 JS 对象来描述 DOM 对象,因为不是真实的 DOM 对象,所以叫 Virtual DOM
可以使用 Virtual DOM 来描述真实 DOM

  • 为什么使用 Virtual DOM
    手动操作 DOM 比较麻烦,还需要考虑浏览器兼容性问题,虽然有 jQuery 等库简化 DOM 操作,但是随着项目的复杂 DOM 操作复杂提升
    为了简化 DOM 的复杂操作于是出现了各种 MVVM 框架,MVVM 框架解决了视图和 状态的同步问题
    为了简化视图的操作我们可以使用模板引擎,但是模板引擎没有解决跟踪状态变化的问 题,于是Virtual DOM 出现了
    Virtual DOM 的好处是当状态改变时不需要立即更新 DOM,只需要创建一个虚拟树来 描述DOM,Virtual DOM 内部将弄清楚如何有效(diff)的更新 DOM
    虚拟 DOM 可以维护程序的状态,跟踪上一次的状态
    通过比较前后两次状态的差异更新真实 DOM

  • 虚拟 DOM 的作用
    维护视图和状态的关系
    复杂视图情况下提升渲染性能
    除了渲染 DOM 以外,还可以实现 SSR(Nuxt.js/Next.js)、原生应用(Weex/React Native)、 小程序(mpvue/uni-app)等

diff算法

把树形结构按照层级分解,只比较同级元素。
给列表结构的每个单元添加唯一的key属性,方便比较。
React 只会匹配相同 class 的 component(这里面的class指的是组件的名字)
合并操作,调用 component 的 setState 方法的时候, React 将其标记为 - dirty.到每一 个事件循环结束, React 检查所有标记 dirty的 component重新绘制.
选择性子树渲染。开发人员可以重写shouldComponentUpdate提高diff的性能

  • 为什么虚拟dom会提高性能
    虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能
  1. 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
  2. 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
  3. 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新

23.New操作符

  1. 创建一个空对象,并且 this 变量引用该对象
  2. 继承该函数的原型
  3. 属性和方法被加入到 this 引用的对象中
  4. 新创建的对象由 this 所引用,并且最后隐式的返回 this
function create(){
	//创建一个空对象
	let obj = new Object();
	//获得构造函数
	let Con = [].shift.call(arguments);
	//链接到原型
	obj.__proto__ = Con.prototype;
	//绑定this,执行构造函数
	let result = Con.apply(obj, arguments)
	//确保new出来的是个对象
	return typeof result === 'object' ? result :obj
}

24.设计模式(发布订阅)

发布-订阅模式也叫做观察者模式。通过一对一或者一对多的依赖关系,当对象发生改变时,订阅方都会收到通知。
点击一个按钮触发了点击事件就是使用了该模式。

25.ES6

ES6对String字符串类型做的常用升级优化

  • 优化部分
    ES6新增了字符串模板,在拼接大段字符串时,用反斜杠()`取代以往的字符串相加的形式,能保留所有空格和换行,使得字符串拼接看起来更加直观,更加优雅
  • 升级部分
    ES6在String原型上新增了includes()方法,用于取代传统的只能用indexOf查找包含字符的方法(indexOf返回-1表示没查到不如includes方法返回false更明确,语义更清晰), 此外还新增了startsWith(), endsWith(), padStart(),padEnd(),repeat()等方法,可方便的用于查找,补全字符串

ES6对Array数组类型做的常用升级优化

  • 优化部分
    数组解构赋值。ES6可以直接以let [a,b,c] = [1,2,3]形式进行变量赋值,在声明较多变量时,不用再写很多let(var),且映射关系清晰,且支持赋默认值
    扩展运算符。ES6新增的扩展运算符(…)(重要),可以轻松的实现数组和松散序列的相互转化,可以取代arguments对象和apply方法,轻松获取未知参数个数情况下的参数集合。(尤其是在ES5中,arguments并不是一个真正的数组,而是一个类数组的对象,但是扩展运算符的逆运算却可以返回一个真正的数组)。扩展运算符还可以轻松方便的实现数组的复制和解构赋值(let a = [2,3,4]; let b = […a])
  • 升级部分
    ES6在Array原型上新增了find()方法,用于取代传统的只能用indexOf查找包含数组项目的方法,且修复了indexOf查找不到NaN的bug([NaN].indexOf(NaN) === -1).此外还新增了copyWithin(),includes(), fill(),flat()等方法,可方便的用于字符串的查找,补全,转换等

ES6对Number数字类型做的常用升级优化

  • 优化部分
    ES6在Number原型上新增了isFinite(), isNaN()方法,用来取代传统的全局isFinite(), isNaN()方法检测数值是否有限、是否是NaN。ES5的isFinite(), isNaN()方法都会先将非数值类型的参数转化为Number类型再做判断,这其实是不合理的,最造成isNaN('NaN') === true的奇怪行为——'NaN’是一个字符串,但是isNaN却说这就是NaN。而Number.isFinite()和Number.isNaN()则不会有此类问题(Number.isNaN(‘NaN’) === false)。(isFinite()同上)
  • 升级部分
    ES6在Math对象上新增了Math.cbrt(),trunc(),hypot()等等较多的科学计数法运算方法,可以更加全面的进行立方根、求和立方根等等科学计算

ES6对Object类型做的常用升级优化

  • 优化部分
    对象属性变量式声明。ES6可以直接以变量形式声明对象属性或者方法,比传统的键值对形式声明更加简洁,更加方便,语义更加清晰;对象的解构赋值;扩展运算符。ES6在Class类里新增了类似this的关键字super。同this总是指向当前函数所在的对象不同,super关键字总是指向当前函数所在对象的原型对象
let [apple, orange] = ['red appe', 'yellow orange'];
let myFruits = {apple, orange};    
// let myFruits = {apple: 'red appe', orange: 'yellow orange'};
  • 升级部分
    ES6在Object原型上新增了is()方法,做两个目标对象的相等比较,用来完善'==='方法。'==='方法中NaN === NaN //false其实是不合理的,Object.is修复了这个小bug。(Object.is(NaN, NaN) // true)
    ES6在Object原型上新增了assign()方法,用于对象新增属性或者多个对象合并
    assign合并的对象target只能合并被合并对象中的自身属性,并不会合并被合并对象的继承属性,也不会合并不可枚举的属性,且无法正确复制get和set属性(会直接执行get/set函数,取return的值)
    ES6在Object原型上新增了getOwnPropertyDescriptors()方法,此方法增强了ES5中getOwnPropertyDescriptor()方法,可以获取指定对象所有自身属性的描述对象。结合defineProperties()方法,可以完美复制对象,包括复制get和set属性
    ES6在Object原型上新增了getPrototypeOf()和setPrototypeOf()方法,用来获取或设置当前对象的prototype对象。这个方法存在的意义在于,ES5中获取设置prototype对像是通过__proto__属性来实现的,然而__proto__属性并不是ES规范中的明文规定的属性,只是浏览器各大产商“私自”加上去的属性,只不过因为适用范围广而被默认使用了,再非浏览器环境中并不一定就可以使用,所以为了稳妥起见,获取或设置当前对象的prototype对象时,都应该采用ES6新增的标准用法
    ES6在Object原型上还新增了Object.keys(),Object.values(),Object.entries()方法,用来获取对象的所有键、所有值和所有键值对数组

26.ES6和commonjs区别

  • CommonJS模块是运行时加载,ES6 Modules是编译时输出接口
  • CommonJS输出是值的拷贝;ES6 Modules输出的是值的引用,被输出模块的内部的改变会影响引用的改变
  • CommonJs导入的模块路径可以是一个表达式,因为它使用的是require()方法;而ES6 Modules只能是字符串
  • CommonJS this指向当前模块,ES6 Modules this指向undefined,且ES6 Modules中没有这些顶层变量:arguments、require、module、exports、__filename、__dirname

27.js各种引入

Script标签引入方式:4种
1.script的标签内写代码
2.a标签href引入
<a href="javascript:alert('hah')">sdfsdf</a>
3.直接写onclick点击事件
<div onclick="alert('我被点击了')"></div>
4.script引入JS文件
<script src="js/day1.js"></script>

二、CSS

1.回流重绘

  • 重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘
  • 回流是布局或者几何属性需要改变就称为回流。
  • 回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。
  • Event loop关联
    当 Eventloop 执行完 Microtasks 后,会判断 document 是否需要更新,因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新一次。
    然后判断是否有 resize 或者 scroll 事件,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。
    判断是否触发了 media query
    更新动画并且发送事件
    判断是否有全屏操作事件
    执行 requestAnimationFrame回调,异步,传入的函数在重绘之前调用
    执行 IntersectionObserver 回调,

2.绝对居中

  1. 绝对定位方法:不确定当前div的宽度和高度,采用transform:translate(-50%,-50%); 当前div的父级添加相对定位(position: relative;)
  2. 绝对定位方法:确定了当前div的宽度,margin值为当前div宽度一半的负值
  3. 绝对定位方法:绝对定位下top left right bottom 都设置0
  4. 绝对定位:calc() 函数动态计算实现水平垂直居中
  5. flex布局方法:当前div的父级添加flex css样式
    display:flex;
    align-items:center;
    justify-content:center;
  6. table-cell实现水平垂直居中: 父元素设置
    display: table-cell;
    vertical-align: middle;
    text-align: center;

3.rem,em,px,vw,vh

  • px:绝对单位,页面按精确像素展示。
  • em:相对单位,基准点为父节点字体的大小,如果自身定义了font-size按自身来计算(浏 览器默认字体是16px),整个页面内1em不是一个固定的值。
  • rem:相对单位,可理解为”root em”, 相对根节点html的字体大小来计算,CSS3新加属性, chrome/firefox/IE9+支持
  • vw:1vw等于视口宽度的1%。
  • vh:1vh等于视口高度的1%。
  • vmin:选取vw和vh中最小的那个。
  • vmax:选取vw和vh中最大的那个。

4.图片优化

  • 图片本身优化
  1. 减少像素点
  2. 减少每个像素点能够显示的颜色(减少颜色通道数从而减小图片大小)
  • 图片传输优化
  1. 不用图片:很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去 代替。
    图标使用Icon-font。
  2. 用 CDN 加载:对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带 宽。一般图片都,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。
  3. 小图使用 base64 格式
  4. 雪碧图:将多个图标文件整合到一张图片中,缺点是操作量比较大
  5. 选择正确的图片格式:
    对于能够显示 WebP格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的 图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量, 缺点就是兼容性并不好
    小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG代替
  6. 照片使用 JPEG

5.盒模型

content-box:默认值,总宽度 = margin + border + padding + width
border-box:盒子宽度包含 padding 和 border,总宽度 = margin + width
inherit:从父元素继承 box-sizing 属性

6.BFC

  • 块级格式化上下文,容器里面的子元素不会在布局上影响到外面的元素,反之也是如此(按照这个理念来想,只要脱离文档流,肯定就能产生 BFC)。
  • 产生 BFC 方式如下
    float 的值不为 none。
    overflow 的值不为 visible。
    position 的值不为 relative 和 static。
    display 的值为 table-cell, table-caption, inline-block中的任何一个

7.超出文本省略号

  • 单行文本省略号
div {
	width: 200px;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}
  • 文字多行超出显示省略号,该方法适用于WebKit浏览器及移动端。
div {
	width: 200px;
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 3;
	overflow: hidden;
}
  • 跨浏览器兼容方案:
p {
    position:relative;
    line-height:1.4em;
    /* 3 times the line-height to show 3 lines */
    height:4.2em;
    overflow:hidden;
}
p::after {
    content:"...";
    font-weight:bold;
    position:absolute;
    bottom:0;
    right:0;
    padding:0 20px 1px 45px;
}

8.可继承的属性

  • 可继承:
    font
    word-break
    letter-spacing
    text-align
    text-rendering
    word-spacing
    white-space
    text-indent
    text-transform
    text-shadow
    line-height
    color
    visibility
    Cursor
  • 不可继承的样式:
    border
    padding
    margin
    width
    height

9.伪类和伪元素

  • 伪类和伪元素的区别
    伪类表状态
    伪元素是真的有元素
    前者单冒号,后者双冒号
  • 状态性伪类的属性
  1. a:link:未访问的链接
  2. a:visited:已访问的链接
  3. a:hover:鼠标移动到链接上(浮动,悬停)具有悬停效果的组件都可以使用这个属性,比如label,input等
  4. a:active:向被激活的元素添加样式(鼠标指针按下了,但是没有松开的瞬间)
  5. :focus:选择拥有键盘焦点的元素(比如文本框,标签可以放进去,就是有焦点)
  • 结构性伪类的属性
  1. :first-child:选择父元素的第一个子元素
  2. :last-child:选择父元素的最后一个子元素
  3. :nth-child(num):选择某个元素的一个或多个特定的子元素
  4. :nth-last-child(num):选择某个元素的一个或多个特定的子元素,从这个元素的最后一个子元素开始算
  5. :first-of-type:选择一个上级元素下的第一个同类子元素
  • 伪元素选择器属性:
  1. ::selection:选择指定元素中被用户选中的内容
  2. ::before:可以在内容之前插入新内容
  3. ::after:可以在内容之后插入新内容
  4. ::first-line:选择指定选择器的首行
  5. ::first-letter:选择文本的第一个字符

10.Position结合Z-index

z-index>0
z-index:auto/z-index:0
inline/inline-block水平盒子
float浮动盒子
block块级水平盒子
z-index<0
层叠上下文元素的背景和边框

11.三栏布局

圣杯
双飞翼

12.Flex特性

  • flex:1 === flex: 1 1 任意数字+任意长度单位;
  1. flex-grow:项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
  2. flex-shrink:项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
  3. flex-basis:给上面两个属性分配多余空间之前, 计算项目是否有多余空间, 默认值为 auto, 即项目本身的大小
  • flex-direction可决定主轴的方向,有四个可选值:
  1. row(默认值):主轴为水平方向,起点在左端。
  2. row-reverse:主轴为水平方向,起点在右端。
  3. column:主轴为垂直方向,起点在上沿。
  4. column-reverse:主轴为垂直方向,起点在下沿。
  • justify-content属性定义了项目在主轴上的对齐方式,值如下:
  1. flex-start(默认值):向主轴开始方向对齐。
  2. flex-end:向主轴结束方向对齐。
  3. center:居中。
  4. space-between:两端对齐,项目之间的间隔都相等。
  5. space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
  • align-items属性定义项目在交叉轴上如何对齐,值如下:
  1. flex-start:交叉轴的起点对齐。
  2. flex-end:交叉轴的终点对齐。
  3. center:交叉轴的中点对齐。
  4. baseline: 项目的第一行文字的基线对齐。
  5. stretch(默认值):如果项目未设置高度或设为 auto,将占满整个容器的高度。

13.外边距折叠以及解决

在CSS当中,相邻的两个盒子(可能是兄弟关系也可能是祖先关系)的外边距可以结合成一个单独的外边距。这种合并外边距的方式被称为折叠,并且因而所结合成的外边距称为折叠外边距。

  • 折叠结果遵循下列计算规则:
    两个相邻的外边距都是正数时,折叠结果是它们两者之间较大的值。
    两个相邻的外边距都是负数时,折叠结果是两者绝对值的较大值。
    两个外边距一正一负时,折叠结果是两者的相加的和。

14.移动端响应式布局

  • 响应式布局:写一套css样式可以兼容多个终端设备,为移动互联网的开发做出了重要贡献
    优点:一套样式可以兼容多个终端设备,灵活性更强
    缺点:工作量增加,容易造成代码冗余,影响加载速度
    核心原理:主要使用css3的媒体查询实现不同设备的响应
  • 媒体查询:
    外联式
    <link rel="stylesheet" media="screen and (min-width:960px)" bhref="red.css"/>
    <link rel="stylesheet" media="screen and (max-width:960px)" href="blue.css"/>
    内联式
    @media screen and (min-width:960px){}
    @media screen and (max-width:960px){}
  • 常见移动端布局方案
  1. 百分比布局(流式布局):以百分比为主要单位进行页面布局
    特点:文字流式,控件弹性,图片等比例缩放,顶部和底部不管分辨率怎么变化,高 度都不变,适用于页面布局比较简单的移动端项目
  2. rem布局(等比例缩放布局)
    特点:使用rem为主要单位进行页面布局,很好的实现了在页面在不同尺寸设备上等 比例缩放
  3. 混合式布局
    特点:将多种布局方式混合在一起使用,实现移动端屏幕的适配

15.CSS动画

依靠CSS3中提出的三个属性:transition、transform、animation

  • transition:定义了元素在变化过程中是怎么样的,包含transition-property、
    transition-duration、 transition-timing-function、transition-delay。
  • transform:定义元素的变化结果,包含rotate、scale、skew、translate。
  • animation:动画定义了动作的每一帧(@keyframes)有什么效果,包括animation-name, animation-duration、animation-timing-function、animation-delay、 animation-iteration-count、animation-direction

16.隐藏元素

  • display: none; 元素会变得不可见,并且不会再占用文档的空间。

  • visibility: hidden; 这个属性只是简单的隐藏某个元素,元素占用的空间仍然存在,但不可点击

  • opacity: 0; CSS3属性,设置0可以使一个元素完全透明,可点击

  • position: absolute; 设置一个很大的 left 负值定位,使元素定位在可见区域之外

  • transform: scale(0); 将一个元素设置为缩放无限小,元素将不可见,元素原来所在的位置将被保留

  • HTML5属性,效果和display:none;相同,但这个属性用于记录一个元素的状态
  • height: 0; 将元素高度设为 0 ,并消除边框

  • filter: blur(0); CSS3属性,将一个元素的模糊度设置为0,从而使这个元素“消失”在页面中

17.样式优先级

  1. !important
  2. 行内样式
  3. ID选择器
  4. 类选择器
  5. 标签
  6. 通配符
  7. 继承
  8. 浏览器默认样式

三、HTML

1.meta标签作用

可提供有关页面的元信息(meta-information),比如针对搜索引擎和更新频度的描述和关键词。通俗点说就是可以理解为提供了关于网站的各种信息。

  1. name属性
    name属性主要用于描述网页,与之对应的属性值为content,content中的内容主要是便于搜索引擎机器人查找信息和分类信息用的。
    meta标签的name属性语法格式是:
    <meta name="参数"content="具体的参数值">。
    其中name属性主要有以下几种参数: 
    A、Keywords(关键字) 
    说明:keywords用来告诉搜索引擎你网页的关键字是什么。
    举例:<meta name="keywords"content="science,education,culture,politics,ecnomics,relationships,entertaiment,human">
    B、description(网站内容描述)
    说明:description用来告诉搜索引擎你的网站主要内容。
    举例:<meta name="description"content="Thispageisaboutthemeaningofscience,education,culture.">
    C、robots(机器人向导)
    说明:robots用来告诉搜索机器人哪些页面需要索引,哪些页面不需要索引。
    content的参数有all,none,index,noindex,follow,nofollow。默认是all。
    举例:<meta name="robots"content="none">
    D、author(作者)
    说明:标注网页的作者
    举例:<meta name="author"content="root,root@xxxx.com">
  2. http-equiv属性
    http-equiv顾名思义,相当于http的文件头作用,它可以向浏览器传回一些有用的信息,以帮助正确和精确地显示网页内容,与之对应的属性值为content,content中的内容其实就是各个参数的变量值。
    meta标签的http-equiv属性语法格式是:
    <meta http-equiv="参数"content="参数变量值">;
    其中http-equiv属性主要有以下几种参数:
    A、Expires(期限)
    说明:可以用于设定网页的到期时间。一旦网页过期,必须到服务器上重新传输。
    用法:<meta http-equiv="expires"content="Fri,12Jan200118:18:18GMT">
    注意:必须使用GMT的时间格式。
    B、Pragma(cache模式)
    说明:禁止浏览器从本地计算机的缓存中访问页面内容。
    用法:<meta http-equiv="Pragma"content="no-cache">
    注意:这样设定,访问者将无法脱机浏览。
    C、Refresh(刷新)
    说明:自动刷新并指向新页面。
    用法:<meta http-equiv="Refresh"content="2;URL=http://www.jb51.net">(注意后面的引号,分别在秒数的前面和网址的后面)
    注意:其中的2是指停留2秒钟后自动刷新到URL网址。
    D、Set-Cookie(cookie设定)
    说明:如果网页过期,那么存盘的cookie将被删除。
    用法:<meta http-equiv="Set-Cookie"content="cookievalue=xxx;expires=Friday,12-Jan-200118:18:18GMT;path=/">
    注意:必须使用GMT的时间格式。
    E、Window-target(显示窗口的设定)
    说明:强制页面在当前窗口以独立页面显示。
    用法:<meta http-equiv="Window-target"content="_top">
    注意:用来防止别人在框架里调用自己的页面。
    F、content-Type(显示字符集的设定)
    说明:设定页面使用的字符集。
    用法:<meta http-equiv="content-Type"content="text/html;charset=gb2312">
    G、content-Language(显示语言的设定)
    用法:<meta http-equiv="Content-Language"content="zh-cn"/>
    H、Cache-Control指定请求和响应遵循的缓存机制。
    Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令包括no-cache、no-store、max-age、max-stale、min-fresh、on
    ly-if-cached,响应消息中的指令包括public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age。各个消息中的指令含义如下
    Public指示响应可被任何缓存区缓存
    Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效
    no-cache指示请求或响应消息不能缓存
    no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
    max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应
    min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应
    max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。

2.meta的viewport

<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
width 设置viewport宽度,为一个正整数,或字符串‘device-width’
device-width 设备宽度
height 设置viewport高度,一般设置了宽度,会自动解析出高度,可以不用设置
initial-scale 默认缩放比例(初始缩放比例),为一个数字,可以带小数
minimum-scale 允许用户最小缩放比例,为一个数字,可以带小数
maximum-scale 允许用户最大缩放比例,为一个数字,可以带小数
user-scalable 是否允许手动缩放

3.语义化

用正确的标签做正确的事情

  • HTML语义化就是让页面的内容结构化,便于对浏览器、搜索引擎解析;
  • 在没有样式CSS情况下也以一种文档格式显示,并且是容易阅读的。
  • 搜索引擎的爬虫依赖于标记来确定上下文和各个关键字的权重,利于 SEO。
  • 使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解

4.SEO

合理的title、description、keywords:搜索对着三项的权重逐个减小,title值强调重点即可, 重要关键词出现不要超过2次,而且要靠前,不同页面title要有所不同;description把 页面内容高度概括,长度合适,不可过分堆砌关键词,不同页面description有所不同; keywords列举出重要关键词即可
语义化的HTML代码,符合W3C规范:语义化代码让搜索引擎容易理解网页
重要内容HTML代码放在最前:搜索引擎抓取HTML顺序是从上到下,有的搜索引擎对抓取 长度有限制,保证重要内容一定会被抓取
重要内容不要用js输出:爬虫不会执行js获取内容
少用iframe:搜索引擎不会抓取iframe中的内容
非装饰性图片必须加alt
提高网站速度:网站速度是搜索引擎排序的一个重要指标

5.Head标签都有哪些

<head> 标签用于定义文档的头部,它是所有头部元素的容器。 中的元素可以引用脚本、指示浏览器在哪里找到样式表、提供元信息等等。
文档的头部描述了文档的各种属性和信息,包括文档的标题、在 Web 中的位置以及和其他文档的关系等。绝大多数文档头部包含的数据都不会真正作为内容显示给读者。
下面这些标签可用在 head 部分:<base>, <link>,<meta>, <script>, <style>, 以及 <title>
<title> 定义文档的标题,它是 head 部分中唯一必需的元素。

6.把script放到head里的影响

阻塞渲染
浏览器在解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。想首屏渲染的越快,就越不应该在首屏就加载 JS文件,这也是都建议将 script 标签放在 body 标签底部的原因。可通过给 script 标签添加 defer 或者 async 属性来回避这个问题

四.网络通信协议

1.https和http

  • HTTPS 要比 HTTPS 多了secure安全性这个概念,实际上, HTTPS 并不是一个新的应用层协议,它其实就是HTTP + TLS/SSL协议组合而成,而安全性的保证正是 SSL/TLS 所做的工作。
  • SSL:安全套接层(Secure Sockets Layer)
  • TLS:传输层安全(Transport Layer Security)建立在SSL 3.0协议规范之上,所支持的加密算法不同,所以TLS与SSL3.0不能互操作
  • https(ssl)加密是发生在应用层与传输层之间,所以在传输层看到的数据才是经过加密的,而我们捕捉到的http post,是应用层的数据,此时还没有经过加密。
    区别:
  • HTTP 是明文传输协议,HTTPS 协议是由 SSL/TLS+HTTP 协议构建的可进行加密传输、 身份认证的网络协议,比 HTTP 协议安全。
  • HTTPS比HTTP更加安全,对搜索引擎更友好,利于SEO,优先索引HTTPS网页。
  • HTTPS标准端口443,HTTP标准端口80。
  • HTTPS需要用到SSL证书,而HTTP不用。
  1. Client发起一个HTTPS请求,连接443端口。这个过程可以理解成是请求公钥的过程。
  2. Server端收到请求后,通过第三方机构私钥加密,会把数字证书(也可以认为是公钥证书) 发送给Client。
  3. 浏览器安装后会自动带一些权威第三方机构公钥,使用匹配的公钥对数字签名进行解密。
    根据签名生成的规则对网站信息进行本地签名生成,然后两者比对。
    通过比对两者签名,匹配则说明认证通过,不匹配则获取证书失败。
  1. 在安全拿到服务器公钥后,客户端Client随机生成一个对称密钥,使用服务器公钥(证书 的公钥)加密这个对称密钥,发送给Server(服务器)。
  2. Server(服务器)通过自己的私钥,对信息解密,至此得到了对称密钥,此时两者都拥有了 相同的对称密钥。
  • 非对称加密中,我们需要明确的点是
    有一对秘钥,公钥和私钥。
    公钥加密的内容,只有私钥可以解开,私钥加密的内容,所有的公钥都可以解开,这 里说的公钥都可以解开,指的是一对秘钥。
    公钥可以发送给所有的客户端,私钥只保存在服务器端。

对称加密:算法公开、计算量小、加密速度快、加密效率高;在数据传送前,发送方和接收方必须商定好秘钥,然后 使双方都能保存好秘钥。其次如果一方的秘钥被泄露,那么加密信息也就不安全了。另外,每对用户每次使用对称加密算法时,都需要使用其他人不知道的唯一秘钥,这会使得收、发双方所拥有的钥匙数量巨大,密钥管理成为双方的负担。
非对称加密:安全但是速度较慢

防止SSL中间人攻击(本质上是不会有中间人攻击的)
第一:访问时网址栏显示:https:// 。
第二:浏览器显示醒目安全锁,点击安全锁,可查看网站、企业的真实身份。
第三:使用了EV SSL证书的网站,显示绿色地址栏,并在网址栏出现企业名称。
其次,采用权威CA机构颁发的受信任的SSL证书。数字证书颁发机构CA是可信任的第三方,在验证申请者的真实身份后才会颁发SSL证书,可以说是保护用户信息安全的第一道关口。
最后,对SSL证书的证书链进行校验。如果是浏览器能识别的SSL证书,则需要检查此SSL证书中的证书吊销列表,如果此证书已经被证书颁发机构吊销,则会显示警告信息:“此组织的证书已被吊销。安全证书问题可能显示试图欺骗您或截获您向服务器发送的数据。建议关闭此网页,并且不要继续浏览该网站。”
如果以上都没有问题,浏览器还会查询此网站是否已经被列入欺诈网站黑名单,如果有问题也会显示警告信息。

2.http协议各版本区别

HTTP 1.0

  • 任何格式的内容都可以发送,这使得互联网不仅可以传输文字,还能传输图像、视频、二进制等文件。
  • 除了GET命令,还引入了POST命令和HEAD命令。
  • http请求和回应的格式改变,除了数据部分,每次通信都必须包括头信息(HTTP header), 用来描述一些元数据。
  • 只使用 header 中的 If-Modified-Since 和 Expires 作为缓存失效的标准。
  • 不支持断点续传,也就是说,每次都会传送全部的页面和数据。
  • 通常每台计算机只能绑定一个IP,所以请求消息中的 URL并没有传递主机名hostname

HTTP 1.1

  • http1.1是目前最为主流的http协议版本,从1999年发布至今,仍是主流的http协议 版本。
  • 引入了持久连接( persistent connection),即TCP连接默认不关闭,可以被多个请求 复用,不用声明Connection: keep-alive。长连接的连接时长可以通过请求头中的 keep-alive 来设置
  • 引入了管道机制( pipelining),即在同一个TCP连接里,客户端可以同时发送多个 请求,进一步改进了HTTP协议的效率。
  • ⭐HTTP 1.1 中新增加了 E-tag,If-Unmodified-Since, If-Match, If-None-Match 等缓存控制标头来控制缓存失效。
  • 支持断点续传,通过使用请求头中的 Range 来实现。
  • 使用了虚拟网络,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。
  • 新增方法:PUT、 PATCH、 OPTIONS、 DELETE。
  • http1.x版本问题
    在传输数据过程中,所有内容都是明文,客户端和服务器端都无法验证对方的身份, 无法保证数据的安全性。
    HTTP/1.1 版本默认允许复用TCP连接,但是在同一个TCP连接里,所有数据通信是按次序进行的,服务器通常在处理完一个回应后,才会继续去处理下一个,这样子就会造成队头阻塞。
    http/1.x 版本支持Keep-alive,用此方案来弥补创建多次连接产生的延迟,但是同样会给服务器带来压力,并且的话,对于单文件被不断请求的服务,Keep-alive会极大影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间。

HTTP 2.0

  • 二进制分帧:头信息和数据体都是二进制,并且统称为"帧":头信息帧和数据帧。
  • 头部压缩: HTTP 1.1版本会出现 User-Agent、Cookie、Accept、Server、Range 等字段可 能会占用几百甚至几千字节,而 Body 却经常只有几十字节,所以导致头部偏重。HTTP 2.0 使用 HPACK 算法进行压缩。
  • 多路复用 复用TCP连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,且不用按顺序一一对应,这样子解决了队头阻塞的问题。
  • 服务器推送 允许服务器未经请求,主动向客户端发送资源,即服务器推送。
  • 请求优先级 可以设置数据帧的优先级,让服务端先处理重要资源,优化用户体验。

HTTP 3

  • 使用UDP作为传输层进行通信
  • 从协议本身保证了安全性,QUIC在建立连接的握手过程中就完成了TLS加密握手
  • 建立连接快,正常只需要1RTT即可建立连接。如果有缓存之前的secret信息,则直接验证和建立连接,此过程0RTT。建立连接时,也可以带有少量业务数据。

RTT为数据完全发送完(完成最后一个比特推送到数据链路上)到收到确认信号的时间。
不和具体底层连接绑定,QUIC为每个连接的两端分别分配了一个唯一ID,上层连接只认这对逻辑ID。网络切换或者断连时,只需要继续发送数据包即可完成连接的建立

  • 使用QPACK进行头部压缩,因为HPACK要求传输过程有序,这会导致队头阻塞。而QPACK不存在这个问题
  • HTTP/3在header中定义了一个新header:Alt-Svc: h3=":20003":表示服务器在20003端口开了一个20003端口用于HTTP/3服务

3.状态码

  • 1xx 信息性状态码 websocket upgrade
  • 2xx 成功状态码
    200 服务器已成功处理了请求
    204(没有响应体)
    206(范围请求 暂停继续下载)
  • 3xx 重定向状态码
    301 比较常用的场景是使用域名跳转。302 用来做临时跳转,比如未登陆的用户访问用户中心时重定向到登录页面
    301(永久) :请求的页面已永久跳转到新的url
    302(临时) :允许各种各样的重定向,一般情况下都会实现为到 GET 的重定向,但是不能确保 POST 会重定向为 POST;由于这样的重定向是临时的, 客户端应当继续向原有地址发送以后的请求。只有在 Cache-Control 或 Expires 中进行了 指定的情况下,这个响应才是可缓存的
    303 只允许任意请求到 GET 的重定向
    304 未修改:自从上次请求后,请求的网页未修改过 缓存命中后返回304
    307:307 和 302 一样,除了不允许 POST 到 GET 的重定向
  • 4xx 客户端错误状态码
    400 客户端参数错误
    401 没有登录
    403 登录了没权限 比如管理系统
    404 页面不存在
    405 禁用请求中指定的方法
    406:API请求的表述格式并不被Web API所支持
    422:实体的Content Type和语法都没问题,但是服务器仍然无法处理这个实体数据,表示语 意上有错误,通常就表示实体验证的错误。
  • 5xx 服务端错误状态码
    500 服务器错误:服务器内部错误,无法完成请求
    502 错误网关:服务器作为网关或代理出现错误
    503 服务不可用:服务器目前无法使用
    504 网关超时:网关或代理服务器,未及时获取请求

4.Ajax

  1. 创建XMLHttpRequest 对象。var ajaxObj = new XMLHttpRequest();
  2. 使用open方法设置请求的参数。open(method, url, 是否异步//默认为true)
  3. 发送请求。想要使用post提交数据,必须添加xhr.setRequestHeader(header,value)
  4. 注册事件。 注册onreadystatechange事件,状态改变时就会调用。如果要在数据完整请 求回来的时候才调用,我们需要手动写一些判断的逻辑。
  5. 获取返回的数据,更新UI。

XMLHttpRequest是ajax的核心机制,一共有三个属性:

  1. onreadystatechange
    xmlhttp.onreadystatechange = function(){};
    每次readyState对象的改变都会触发一次onreadystatechange属性储存的函数,一 次有效的Ajax请求一共会触发5次该事件
  2. readyState
    一共有5个值,0~4分别代表不同的状态。
    0——请求未初始化
    1——服务器已建立了链接
    2——请求已被服务器接收
    3——请求正在被处理
    4——请求完成处理,响应就绪
    if(xmlhttp.readyState==4…):用以判断当前服务器是否已准备好响应
  3. Status
    代表着Http状态码 200:OK
    if(xmlhttp.readyState==4&& xmlhttp.status==200)

5.跨域

同源策略:限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个 用于隔离潜在恶意文件的关键的安全机制。
源:包括三个部分:协议、域名、端口(http协议的默认端口是80)。如果有任何一个部 分不同,则源不同,那就是跨域了。
限制:这个源的文档没有权利去操作另一个源的文档。这个限制体现在:(要记住)
Cookie、LocalStorage和IndexDB无法获取。
无法获取和操作DOM。
不能发送Ajax请求。我们要注意,Ajax只适合同源的通信。
跨域通信的方式:

  1. JSONP
    通过<script>标签的异步加载来实现的。比如说,实际开发中,我们发现,head 标签里,可以通过<script>标签的src,里面放url,加载很多在线的插件。这就 是用到了JSONP。function(url, data, func)
    使用简单且兼容性不错,但是只限于get请求。
  2. WebSocket
  3. CORS
    服务端设置Access-Control-Allow-Origin就可以开启 CORS
    简单请求:GET、HEAD、POST
    Content-Type仅限于下列三者之一:
    text/plain
    multipart/form-data
    application/x-www-form-urlencoded
    复杂请求:首先会发起一个预检请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。
    该请求会验证你的 Authorization 字段,没有的话就会报错。
    当前端发起了复杂请求后,你会发现就算你代码是正确的,返回结果 也永远是报错的。因为预检请求也会进入回调中,也会触发 next 方法,因为预检请求并不包含 Authorization 字段,所以服务端会报错。
    想解决这个问题很简单,只需要在回调中过滤 option 方法即可
  4. document.domain:该方式只能用于主域名相同的情况下
  5. Hash
    url的#后面的内容就叫Hash。Hash改变,页面不会刷新。
    url的?后面的内容叫Search。Search的改变会导致页面刷新,不能做跨域通信。
  6. postMessage()
    H5中新增方法,可以用来做跨域通信。

6.请求头都有哪些

  1. GET:它本质就是发送一个请求来取得服务器上的某一资源。GET方式提交的数据最多只 能是1024字节。
  2. POST:向服务器提交数据。这个方法用途广泛,几乎目前所有的提交操作都是靠这个完成。
  3. PUT:和POST极为相似,都是向服务器发送数据,但它们之间有一个重要区别,PUT通常 指定了资源的存放位置,而POST则没有,POST的数据存放位置由服务器自己决定。
  4. DELETE:删除某一个资源。
  5. HEAD:和GET本质是一样的,区别在于HEAD不含有呈现数据,而仅仅是HTTP头信息。
  6. OPTIONS:用于获取当前URL所支持的方法。若请求成功,则它会在HTTP头中包含一个 名为“Allow”的头,值是所支持的方法,如“GET, POST”。
  • GET和POST的区别
    本质上,只是语义上的区别,GET 用于获取资源,POST 用于提交资源。
    从缓存角度看,GET 请求后浏览器会主动缓存,POST 默认情况下不能。
    从参数角度来看,GET请求一般放在URL中,因此不安全,POST请求放在请求体中,相对 而言较为安全,但是在抓包的情况下都是一样的。为了防止CSRF攻击,很多公司把get 统一改成了post。get请求的参数,会保留在浏览器的历史记录里,而post不会。
    从编码角度看,GET请求只能经行URL编码,只能接受ASCII码,而POST支持更多的编码 类型且不对数据类型限值。
    GET请求幂等,POST请求不幂等,幂等指发送 M 和 N 次请求(两者不相同且都大于1), 服务器上资源的状态一致。
    GET请求会一次性发送请求报文,POST请求通常分为两个TCP数据包,首先发 header 部 分,如果服务器响应 100(continue), 然后发 body 部分。
    浏览器在回退时,get 不会重新请求,但是post会重新请求。

阻止get请求的缓存: 在URL最后面加上时间戳 jQuery全局设置禁止缓存cache : false

7.共享cookie(免登录实现)

登录成功时setCookie

function setCookie(){
    let userName = 'admin'; //用户名
    let passWord = '1'; //密码
    let cookieName = 'userInfo'; //cookie名称
    let data = {
        userName: userName,
        passWord: passWord
    }
    let d = new Date();
    let saveTime = 7 //cookie保存时间(天)
    d.setDate(d.getDate() + saveTime)
    document.cookie = `${cookieName}=${JSON.stringify(data)};path=/;expires=${d.toUTCString()}`
}

登录时验证cookie是否存在

function getCookie(){
    let cookie = document.cookie;
    let cookieName = 'userInfo';//cookie名称
    let arr = cookie.split('; ');//把cookie和时间戳分开
    let userInfo = null;
    for(let i = 0;i<arr.length;i++){
        let tempArr = arr[i].split('=');//把cookie和data拆开
        if(tempArr[0] = cookieName){
            userInfo = JSON.parse(tempArr[1])
        }
    }
    if(userInfo){
        //cookie存在,可以写跳转语句
    }
    else{
        //cookie不存在所以不做操作
    }
}

8.Cookie和token

Cookie是由用户客户端计算机暂时或永久保存的信息,保存地址为C:\Users\用户名\AppData\Local\Microsoft\Windows\INetCookies

  • Jwt实现token 加密:
    需要一个 secret(随机数)
    后端利用 secret 和加密算法(如:HMAC-SHA256)对 payload(如账号密码) 生成一个字符 串(token),返回前端
    前端每次 request 在 header 中带上 token
    后端用同样的算法解密
  • 安全:
    攻击者通过 xss 拿到用户的 cookie 然后就可以伪造 cookie 了
    或者通过 csrf 在同个浏览器下面通过浏览器会自动带上 cookie 的特性在通过 用户网站-攻击者网站-攻击者请求用户网站的方式 浏览器会自动带上cookie
    但是 token不会被浏览器带上,token 是放在 jwt 里面下发给客户端的 而且存储地方不定,不能通过document.cookie 直接拿到,通过 jwt+ip 的方式可以防止被劫持,即使被劫持也是无效的 jwt

9.cookie和session(会话)

session

  • Session是服务器端使用的一种记录客户端状态的机制使用上比Cookie简单一些,相应的也增加了服务器的存储压力。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
  • 当多个客户端执行程序时,服务器会保存多个客户端的Session。获取Session的时候也不需要声明获取谁的Session。Session机制决定了当前客户只会获取到自己的Session,而不会获取到别人的Session。各客户的Session也彼此独立,互不可见。
HttpSession
session = request.getSession(); 
// 获取Session对象
session.setAttribute("loginTime", new Date());     
// 设置Session中的属性
out.println("登录时间为:"+(Date)session.getAttribute("loginTime"));      
// 获取Session属性
  • Session的生命周期
    Session保存在服务器端。为了获得更高的存取速度,服务器一般把Session放在内存里。每个用户都会有一个独立的Session。如果Session内容过于复杂,当大量客户访问服务器时可能会导致内存溢出。因此,Session里的信息应该尽量精简
    Session在用户第一次访问服务器的时候自动创建。需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session。如果尚未生成Session,也可以使用request.getSession(true)强制生成Session。
    Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session“活跃(active)”了一次。
    由于会有越来越多的用户访问服务器,因此Session也会越来越多。为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。
    Session的超时时间为maxInactiveInterval属性,可以通过对应的getMaxInactiveInterval()获取,通过setMaxInactiveInterval(longinterval)修改。
    Session的超时时间也可以在web.xml中修改。另外,通过调用Session的invalidate()方法可以使Session失效。
  • 虽然Session保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。这是因为Session需要使用Cookie作为识别标志HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为JSESSIONID的Cookie,它的值为该Session的id(也就是HttpSession.getId()的返回值)。Session依据该Cookie来识别是否为同一用户
  • URL地址重写
    URL地址重写是对客户端不支持Cookie的解决方案。URL地址重写的原理是将该用户Session的id信息重写到URL地址中。服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。

cookie和session的区别

  1. cookie数据存放在客户的浏览器上,session数据放在服务器上。
  2. cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。
  3. 设置cookie时间可以使cookie过期。但是使用session-destory(),我们将会销毁会话。
  4. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie。
  5. 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。(Session对象没有对存储的数据量的限制,其中可以保存更为复杂的数据类型)

10.Tcp三次握手四次挥手

  • 三次握手:
    客户端和服务端都需要确认各自可收发,因此需要三次握手
  1. 第一次握手成功让服务端知道了客户端具有发送能力
  2. 第二次握手成功让客户端知道了服务端具有接收和发送能力,但此时服务端并不知道客户端 是否接收到了自己发送的消息
  3. 第三次握手成功让服务端知道了客户端具有接收能力。对于客户端来说,已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也是没问题的。
  • 四次挥手:
  1. 客户端要求断开连接,发送断开的请求。
  2. 服务端收到请求,然后给客户端一个响应。
  3. 服务端经过一个等待,处理完剩余内容后确定可以关闭连接了,再发一条FIN给客户端。
  4. 客户端收到服务端的 FIN,再给服务端发送一个 ACK。等待2MSL(一个 TCP 分段可以存在于互联网系统中的最大时间)后断开

11.Tcp和udp的区别

  1. TCP 是面向连接的,udp 是无连接的即发送数据前不需要先建立链接
  2. TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失, 不重复, 且按序到达;UDP 尽最大努力交付,即不保证可靠交付。 并且因为 tcp 可靠, 面向连 接,不会丢失数据因此适合大数据量的交换
  3. TCP 是面向字节流,UDP 面向报文,并且网络出现拥塞不会使得发送速率降低(因 此会出 现丢包,对实时的应用比如 IP 电话和视频会议等)
  4. TCP 只能是 1 对 1 的,UDP 支持 1 对 1,1 对多
  5. TCP 的首部较大为 20 字节,而 UDP 只有 8 字节
  6. TCP 是面向连接的可靠性传输,而 UDP 是不可靠的

12.计算机网络五层基于什么协议

  • 五层:TCP/IP协议——物理层,网络接入层,互联网层(IP),运输层(TCP/UDP),应用层(HTTP)
  • 七层:OSI协议——物理层,数据链路层,网络层,运输层,会话层,表示层,应用层

13.CDN

CDN的原理是尽可能的在各个地方分布机房缓存数据,这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。
因此,我们可以将静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。并且对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie,平白消耗流量

五.浏览器

1.url到页面全流程

  1. 在浏览器地址栏输入URL
  2. 浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤,如果资源未缓存,发起新请求
  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不一样)

DNS 的作用就是通过域名查询到具体的 IP。
因为 IP 存在数字和英文的组合(IPv6),很不利于人类记忆,所以就出现了域名。你可以把域名看成是某个 IP 的别名,DNS 就是去查询这个别名的真正名称是什么
在 TCP 握手之前就已经进行了 DNS 查询,这个查询是操作系统自己做的。当你在浏览器中想访问 www.google.com 时,会进行一下操作
操作系统会首先在本地缓存中查询
没有的话会去系统配置的 DNS 服务器中查询
如果这时候还没得话,会直接去 DNS 根服务器查询,这一步查询会找出负责 com 这个一级域名的服务器
然后去该服务器查询 google 这个二级域名
接下来三级域名的查询其实是我们配置的,你可以给 www 这个域名配置一个 IP,然后还可以给别的三级域名配置一个 IP
以上介绍的是 DNS 迭代查询,还有种是递归查询,区别就是前者是由客户端去做请求,后者是由系统配置的 DNS 服务器做请求,得到结果后将数据返回给客户端。

  1. 打开一个socket与目标IP地址,端口建立TCP链接,三次握手
  2. TCP链接建立后发送HTTP请求
  3. 服务器接受请求并解析,将请求转发到服务程序,如虚拟主机使用HTTP Host头部判断请求的服务程序
  4. 服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
  5. 处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
  6. 服务器将响应报文通过TCP连接发送回浏览器
  7. 浏览器接收HTTP响应,然后根据情况选择关闭TCP连接或者保留重用,
  8. 浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,这些情况处理与2XX不同
  9. 如果资源可缓存,进行缓存
  10. 对响应进行解码(例如gzip压缩)
  11. 根据资源类型决定如何处理(假设资源为HTML文档)
  12. 解析HTML文档,构件DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序
  13. 构建DOM树:
    Tokenizing:根据HTML规范将字符流解析为标记
    Lexing:词法分析将标记转换为对象并定义属性和规则
    DOM construction:根据HTML标记关系将对象组成DOM树
  14. 解析过程中遇到图片、样式表、js文件,启动下载
  15. 构建CSSOM树:
    Tokenizing:字符流转换为标记流
    Node:根据标记创建节点
    CSSOM:节点创建CSSOM树
  16. 根据DOM树和CSSOM树构建渲染树 (opens new window):
    从DOM树的根节点遍历所有可见节点,不可见节点包括:1)script,meta这样本身不可见的标签。2)被css隐藏的节点,如display: none
    对每一个可见节点,找到恰当的CSSOM规则并应用
    发布可视节点的内容和计算样式
  17. js解析如下:
    22.1浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loading
    22.2HTML解析器遇到没有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事件
  18. 显示页面(HTML解析过程中会逐步显示页面)

2.页面渲染阻塞

  • 首先渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。如果你想渲染的越快,你越应该降低一开始需要渲染的文件大小,并且扁平层级,优化选择器。
  • 然后当浏览器在解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS文件,这也是都建议将 script 标签放在 body 标签底部的原因。
    当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。
    当 script 标签加上 defer 属性以后,表示该 JS 文件会并行下载,但是会放到 HTML 解析完成后顺序执行,所以对于这种情况你可以把 script标签放在任意位置。
    对于没有任何依赖的 JS 文件可以加上 async 属性,表示 JS 文件下载和解析不会阻塞渲染。

3.存储

在这里插入图片描述
在这里插入图片描述

cookie既可以由服务端来设置,也可以由客户端来设置。
服务端:每个cookie放一个set-cookie字段中。
客户端:仅有document.cookie = “name=value”;方法,需要自己封装
在这里插入图片描述

4.安全

XSS(Cross Site Scripting)跨域脚本攻击

  • 核心原理:不需要你做任何的登录认证,它会通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等)。

  • 后果
    盗用Cookie
    破坏页面的正常结构,插入广告等恶意内容
    D-doss攻击

  • 攻击方式

  1. 反射型
    发出请求时,XSS代码出现在url中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,所以叫反射型XSS。

  2. 存储型
    存储型XSS和反射型XSS的差别在于,提交的代码会存储在服务器端(数据库、内存、文件系统等),下次请求时目标页面时不用再提交XSS代码。

  • 防御方式
  1. 转义字符
    对于用户的输入应该是永远不信任的。最普遍的做法就是转义输入输出的内容,对于引号、尖括号、斜杠进行转义
  2. 过滤
    移除用户输入的和事件相关的属性。如onerror可以自动触发攻击,还有onclick等。(总而言是,过滤掉一些不安全的内容)
    移除用户输入的Style节点、Script节点、Iframe节点。(尤其是Script节点,它可是支持跨域的呀,一定要移除)。
  3. CSP
    本质上就是建立白名单,开发者明确告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截是由浏览器自己实现的。我们可以通过这种方式来尽量减少 XSS 攻击。
    设置 HTTP Header 中的 Content-Security-Policy
    设置 meta 标签的方式 <meta http-equiv="Content-Security-Policy">

CSRF(Cross-site request forgery)跨站请求伪造

  • 原理
    攻击者构造出一个后端请求地址,诱导用户点击或者通过某些途径自动发起请求。如果用户是在登录状态下的话,后端就以为是用户在操作,从而进行相应的逻辑。
  1. 登录受信任网站A,并在本地生成Cookie。(如果用户没有登录网站A,那么网站B在诱导的时候,请求网站A的api接口时,会提示你登录)
  2. 在不登出A的情况下,访问危险网站B(其实是利用了网站A的漏洞)。
  • 防御方式
  1. 使用Get 请求不对数据进行修改
  2. 不让第三方网站访问到用户 Cookie
    以对 Cookie 设置 SameSite 属性。该属性表示 Cookie 不随着跨域请求发送
  3. 阻止第三方网站请求接口
    Referer 验证:只接受本站的请求,服务器才做响应;如果不是,就拦截。
  4. 请求时附带验证信息,比如验证码或者 Token
    服务器发送给客户端一个token;
    客户端提交的表单中带着这个token。
    如果这个 token 不合法,那么服务器拒绝这个请求。

XSS和CSRF的区别

  • 是否需要登录
    CSRF:需要用户先登录网站A,获取 cookie
    XSS:不需要登录。
  • 攻击原理
    CSRF:是利用网站A本身的漏洞,去请求网站A的api。
    XSS:是向网站 A 注入 JS代码,然后执行 JS 里的代码,篡改网站A的内容。

点击劫持

点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击

  • 防御方式
  1. X-FRAME-OPTIONS
    X-FRAME-OPTIONS 是一个 HTTP 响应头,在现代浏览器有一个很好的支持。这个 HTTP 响应头 就是为了防御用iframe 嵌套的点击劫持攻击。
    该响应头有三个值可选,分别是
    DENY,表示页面不允许通过 iframe 的方式展示
    SAMEORIGIN,表示页面可以在相同域名下通过 iframe 的方式展示
    ALLOW-FROM,表示页面可以在指定来源的 iframe 中展示
  2. JS 防御

5.缓存

缓存策略分为两种:强缓存和协商缓存

强缓存

可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control 。强缓存表示在缓存期间不需要请求,state code为 200

  • Expires:是 HTTP/1 的产物,表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT/UTC 后过期,需要再次请求。并且 Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
  • Cache-Control:出现于 HTTP/1.1,优先级高于 Expires。该属性值表示资源会 在 30 秒后过期,需要再次请求。可以在请求头或者响应头中设置, 并且可以组合使用多种指令。

协商缓存

可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag

  • Last-Modified:表示本地文件最后修改日期,If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回304 状态码。
  • ETag 和 If-None-Match:ETag 类似于文件指纹,If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资 源发送回来。并且 ETag优先级比 Last-Modified 高。

对于频繁变动的资源,首先需要使用 Cache-Control: no-cache 使浏览器每次都请求服 务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能 节省请求数量,但是能显著减少响应数据大小。

缓存位置上来说分为四种,并且各自有优先级

  • Service Worker:
    Service Worker是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全
    Service Worker 实现缓存功能一般分为三个步骤:
    首先需要先注册 Service Worker然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。
  • Memory Cache:内存中的缓存,容量小,一旦关闭页面,内存中的缓存也就被释放了。
  • Disk Cach:存储在硬盘中的缓存,读取速度慢点,容量和存储时效性更强。
  • Push Cache:HTTP/2 中的内容,缓存时间很短暂,只在会话(Session)中存在
  • 以上四种全都没有命中时进行网络请求

六.React

1.单向数据流的好处

react的编程思想是严谨且周密的,它约束了我们的操作,这是为了确保我们在使用react构建复杂项目的时候不会出现太多问题。
一旦出现了问题,根源几乎集中在props和state这俩实例属性上。
单向数据流是react规范的数据流向,它的作用是极大的降低了我们组件间通信的代码耦合,让组件间的通信更为清晰,debug直接往props中找。
context方便我们进行组件间的隔代通信。

基于react严谨且周密的编程思想,制订了单向数据流这样的通信约束,使得我们react项目中的数据传递结构稳定且不易耦合,有事没事找props和context解决一切通信问题。

2.组件通信方式

  • 子组件更改父组件内容:
  1. 父组件中定义函数,通过props传递给子组件,子组件调用这个函数并传参。
  2. 使用ref标记子组件,能获取子组件实例的所有数据(包括子组件的props,state,定 义的方法,实例…),父组件通过this.ref就拿到子组件的任何数据了。
  • 逆向:子调用爷爷的函数并传参就实现了子传爷爷;
  • 同级 :子1传父,父传子2 ;
  • 很远关系:
  1. context
  2. 通过第三方媒介共享( 缓存,url,服务端,以及redux,react-redux,dva等全局状态 存储管理插件);

3.函数组件与类组件的区别

  1. 函数组件看似只是一个返回值是DOM结构的函数,其实它的背后是无状态组件(Stateless Components)的思想。函数组件中,你无法使用State,也无法使用组件的生命周期方法,这就决定了函数组件都是展示性组件(Presentational Components),接收Props,渲染DOM,而不关注其他逻辑。
  2. 函数组件中没有this。所以你再也不需要考虑this带来的烦恼。而在类组件中,你依然要记得绑定this这个琐碎的事情。如示例中的sayHi。
  3. 函数组件更容易理解。当你看到一个函数组件时,你就知道它的功能只是接收属性,渲染页面,它不执行与UI无关的逻辑处理,它只是一个纯函数。而不用在意它返回的DOM结构有多复杂。
  4. 性能。目前React还是会把函数组件在内部转换成类组件,所以使用函数组件和使用类组件在性能上并无大的差异。但是,React官方已承诺,未来将会优化函数组件的性能,因为函数组件不需要考虑组件状态和组件生命周期方法中的各种比较校验,所以有很大的性能提升空间。
  5. 函数组件迫使你思考最佳实践。这是最重要的一点。组件的主要职责是UI渲染,理想情况下,所有的组件都是展示性组件,每个页面都是由这些展示性组件组合而成。如果一个组件是函数组件,那么它当然满足这个要求。所以牢记函数组件的概念,可以让你在写组件时,先思考这个组件应不应该是展示性组件。更多的展示性组件意味着更多的组件有更简洁的结构,更多的组件能被更好的复用。
  • React 区分 Class组件 和 Function组件的方式:
    由于所有的类组件都要继承 React.Component,所以只要判断原型链上是否有 React.Component 就可以了
  • 函数组件实现原理
    组件化是最符合js编程者编程习惯的规范。这个要从组件的本质说起。
    类组件的本质,是类,类的本质,是函数; 函数组件的本质也是函数。
    那么,组件的本质=>函数。
    那么组件嵌套组件实例这种方式,便是函数嵌套函数实例化对象

4.无状态组件与纯组件

无状态组件

无状态组件可以通过减少继承Component而来的生命周期函数而达到性能优化的效果。从本质上来说,无状态组件就是一个单纯的render函数,所以无状态组件的缺点也是显而易见的。因为它没有shouldComponentUpdate生命周期函数,所以每次state更新,它都会重新绘制render函数。原则上,只要一个组件只具有render函数时,都可以封装成无状态组件。

纯组件

纯组件是通过控制shouldComponentUpdate生命周期函数,减少render调用次数来减少性能损耗的。这相对于Component来说,减少了手动判断state变化的繁琐操作,但该组件也具有一定的缺陷,因为它只能进行一层浅比较,它只比较props和state的内存地址,如果内存地址相同,则shouldComponentUpdate生命周期就返回false
PureComponent的使用场景应该是局部数据发生改变的场景,比如带有输入框、switch开关等的UI组件就可以使用PureComponent组件封装。PureComponent中如果有数据操作最好配合一个第三方组件——Immutable一起使用,Immutable需要使用npm安装该插件才可以使用,因为Immutable可以保证数据的不变性。

5.React合成事件

  • 事件委托:利用事件的冒泡特性,把多个子元素的同一类型的监听逻辑,合并到父元素上通过一个监听函数来管理。父元素可以通过事件对象中的 e.target 属性,拿到实际触发事件的那个元素,针对这个元素分发事件处理的逻辑,做到真正的“委托”。

  • React 的事件系统沿袭了事件委托的思想。在 React 中,除了少数特殊的不可冒泡的事件(比如媒体类型的事件)无法被事件系统处理外,绝大部分的事件都不会被绑定在具体的元素上,而是统一被绑定在页面的 document 上。当事件在具体的 DOM 节点上被触发后,最终都会冒泡到 document 上,document 上所绑定的统一事件处理程序会将事件分发到具体的组件实例。
    注意:在react17以后,绝大部分的事件都不会被绑定在具体的元素上,而是统一被绑定在页面的 root节点上,而不是document
    在分发事件之前,React 首先会对事件进行包装,把原生 DOM 事件包装成合成事件。

  • 合成事件
    合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力
    对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。当你需要访问原生 DOM 事件对象时,可以通过合成事件对象的 e.nativeEvent 属性来获取。

  • 事件绑定
    是在组件的挂载过程中完成的,具体来说,是在 completeWork 中完成的。completeWork 内部有三个关键动作:

  1. 创建 DOM 节点(createInstance)
  2. 将 DOM 节点插入到 DOM 树中(appendAllChildren)
  3. 为 DOM 节点设置属性(finalizeInitialChildren),设置属性这个环节,会遍历 FiberNode 的 props key。当遍历到事件相关的 props 时,就会触发事件的注册链路。
    若事件系统识别到 listenerMap.has(topLevelType) 为 true,也就是当前这个事件 document 已经监听过了,那么就会直接跳过对这个事件的处理,否则才会进入具体的事件监听逻辑。如此一来,即便我们在 React 项目中多次调用了对同一个事件的监听,也只会在 document 上触发一次注册。
    针对同一个事件,即便可能会存在多个回调,document 也只需要注册一次监听。因为 React最终注册到 document 上的并不是某一个 DOM 节点上对应的具体回调逻辑,而是一个统一的事件分发函数。
  • 事件触发的本质是对 dispatchEvent 函数的调用。
  • 事件回调的收集与执行,收集过程对应的源码逻辑,这部分逻辑在 traverseTwoPhase 函数中:循环收集符合条件的父节点,存进 path 数组中;模拟事件在捕获阶段的传播顺序,收集捕获阶段相关的节点实例与回调函数;模拟事件在冒泡阶段的传播顺序,收集冒泡阶段相关的节点实例与回调函数

6.setState同步还是异步

setState 异步的实现方式:每来一个 setState,就把它塞进一个队列里“攒起来”。等时机成熟,再把“攒起来”的 state 结果做合并,最后只针对最新的 state 值走一次更新流程。这个过程,叫作“批量更新”。
并不是 setTimeout 改变了 setState,而是 setTimeout 帮助 setState “逃脱”了 React 对它的管控。只要是在 React 管控下的 setState,一定是异步的。
源码
setState 并不是单纯同步/异步的,它的表现会因调用场景的不同而不同:在 React 钩子函数及合成事件中,它表现为异步;而在 setTimeout、setInterval 等函数中,包括在 DOM 原生事件中,它都表现为同步。这种差异,本质上是由 React 事务机制和批量更新机制的工作方式来决定的。

合成事件中是异步
钩子函数中的是异步
原生事件中是同步
setTimeout中是同步

传入 setState 函数的第二个参数的作用是什么?
该函数会在 setState 函数调用完成并且组件开始重渲染的时候被调用,我们可以用该 函数来监听渲染是否完成

7.生命周期渲染机制

  • 生命周期:
  1. 挂载卸载过程
    1.1 constructor()
    constructor()中完成了React数据的初始化,它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数。
    1.2 componentWillMount()
    componentWillMount()一般用的比较少,它更多的是在服务端渲染时使用。它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时。
    1.3 componentDidMount()
    组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
    1.4 componentWillUnmount ()
    在此处完成组件的卸载和数据的销毁。
  2. 更新过程
    2.1 componentWillReceiveProps (nextProps)
    在接受父组件改变后的props需要重新渲染组件时用到的比较多
    接受一个参数nextProps,通过对比nextProps和this.props,将nextProps的state为当前组件的state,从而重新渲染组件
    2.2 shouldComponentUpdate(nextProps,nextState)
    主要用于性能优化(部分更新)
    唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新
    因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断
    2.3 componentWillUpdate (nextProps,nextState)
    shouldComponentUpdate返回true以后,组件进入重新渲染的流程,进入componentWillUpdate,这里同样可以拿到nextProps和nextState。
    2.4 componentDidUpdate(prevProps,prevState)
    组件更新完毕后,react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state。
    2.5 render()
    render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。
  3. React新增的生命周期
    3.1 getDerivedStateFromProps(nextProps, prevState)
    代替componentWillReceiveProps()。
    老版本中的componentWillReceiveProps()方法判断前后两个 props 是否相同,如果不同再将新的 props 更新到相应的 state 上去。这样做一来会破坏 state 数据的单一数据源,导致组件状态变得不可预测,另一方面也会增加组件的重绘次数。
    在 componentWillReceiveProps 中,我们一般会做以下两件事,一是根据 props 来更新 state,二是触发一些回调,如动画或页面跳转等。
    在新版本中,官方将更新 state 与触发回调重新分配到了 getDerivedStateFromProps 与 componentDidUpdate 中,使得组件整体的更新逻辑更为清晰。而且在 getDerivedStateFromProps 中还禁止了组件去访问 this.props,强制让开发者去比较 nextProps 与 prevState 中的值,以确保当开发者用到 getDerivedStateFromProps 这个生命周期函数时,就是在根据当前的 props 来更新组件的 state,而不是去做其他一些让组件自身状态变得更加不可预测的事情。
    3.2 getSnapshotBeforeUpdate(prevProps, prevState)
    代替componentWillUpdate。
    常见的 componentWillUpdate 的用例是在组件更新前,读取当前某个 DOM 元素的状态,并在 componentDidUpdate 中进行相应的处理。
    这两者的区别在于
    在 React 开启异步渲染模式后,在 render 阶段读取到的 DOM 元素状态并不总是和 commit 阶段相同,这就导致在
    componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不安全的,因为这时的值很有可能已经失效了。
    getSnapshotBeforeUpdate 会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。
    此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
    在这里插入图片描述

对于如何区别优先级,React 有自己的一套逻辑。对于动画这种实时性很高的东西,也就是 16 ms 必须渲染一次保证不卡顿的情况下,React 会每 16 ms(以内) 暂停一下更新,返回来继续渲染动画
对于异步渲染,现在渲染有两个阶段:reconciliation 和 commit 。前者过程是可以打断的,后者不能暂停,会一直更新界面直到完成。

  1. Reconciliation 阶段:render前执行的函数,被16新增的getDerivedStateFromProps替代
    componentWillMount
    componentWillReceiveProps
    shouldComponentUpdate
    componentWillUpdate
  2. Commit 阶段
    componentDidMount
    componentDidUpdate
    componentWillUnmount
    在这里插入图片描述

因为 Reconciliation 阶段是可以被打断的,所以 Reconciliation 阶段会执行的生命周期函数就可能会出现调用多次的情况,从而引起 Bug。由此对于 Reconciliation 阶段调用的几个函数,除了 shouldComponentUpdate 以外,其他都应该避免去使用,并且 V16 中也引入了新的 API 来解决这个问题。
渲染机制:

  1. 一个组件加载完毕后,如果既没有外部驱动,也没有内部驱动,是不会进行重新渲染的。
  2. 组件想要对自身进行刷新,可以通过调用setState()或者forceUpdate()来实现,这是让组件刷新的内部驱动.
  3. 父组件通过给子组件传递props,告知子组件有可能重新渲染,子组件自己根据传来的数据(在componentWillReceiveProps方法中)决定是否有必要进行重新渲染。
    父组件可以通过props把数据传递给子组件,但需要注意的是这个传递动作只发生render的过程中,在组件已经渲染完毕后,传递会停止。
    加入父组件内部定义了变量A ,并赋给了子组件某个属性,当A的值发生改变,并不足以让子组件刷新。只要A的变化不足以引发父组件调用自身render方法进行重新渲染,就不会传递。
    组件的数据传递需要render驱动,而不是由数据变化驱动的。
    在父组件中调用setState()或者forceUpdate()都可以引起组件对自身的重新渲染。这就完成了第一步,成功把props传给子组件。而子组件决定当接收到外部传来的属性的时候要怎么处理,具体实现在componentWillReceiveProps方法里,在组件首次渲染完毕之后,这个方法会在组件每次接收到外部传来的props的时候被调用。
  4. shouldComponentUpdate方法默认返回true,这就导致了每次更新都重新渲染。
    当React将要渲染组件时他会执行shouldComponentUpdate方法来看它是否返回true(组建应该更新,也就是重新渲染)。你只要重写shouldComponentUpdate方法让他根据情况返回true或者false来告诉React什么时候重新渲染。
  5. React组件生命周期顺序
  • 首次实例化:
    getDefaultProps——>getInitialState——>componentWillMount——>render——>componentDidMount

  • 实例化完成后的更新:
    getInitialState——>componentWillMount——>render——>componentDidMount

  • 组件已存在时的状态改变:
    componentwillReceiveProps——>shouldComponentUpdate——>componentWillUpdate——>render——>omponentDidUpdate

8.React16新特性

  1. render支持返回这五类:React elements, 数组和Fragments,Portal,String/numbers, boolean/null。
  2. Error boundary(错误边界)可以捕获组件在树中比他低的组件错误,记录错误并展示一个回退的UI。
    捕获范围:渲染期间;生命周期内;整个组件树构造函数内。
    无法捕获:事件函数里的错误;异步代码;服务端渲染;Error Boundary自身抛出来的 错误。
    错误边界放在哪里:
    1.可以放在顶层,告诉用户有东西出错。感觉失去了错误边界的意义。因为有一个组 件出错了,其他正常的也没办法正常显示了
    2.包在子组件外面,保护其他应用不崩溃。
  3. react portal:可以帮助我们在JSX中跟普通组件一样直接使用dialog, 但是又可以让dialog 内容层级不在父组件内,而是显示在独立于原来app在外的同层级组件。
  4. 自定义DOM属性:不再忽略未标准化的html和svg属性
  5. 优化SSR:
    生成更简洁的HTML:去掉了很多属性,增加易读性,同时很大程度上减少html的文件大小。
    宽松的客户端:允许属性顺序不一致,而且遇到不匹配的标签,还会做子树的修改,不是整个替换。
    一致性校验无需提前编译:只有一次检查process.env的地方,所以不需要提前编译了
    服务端渲染速度更快
    支持流式渲染:内容以一种流的形式传给前端。所以在下一部分的内容被生成之前,开 头的内容就已经被发到浏览器端了。这样浏览器就可以更早的编译渲染文件内容。
  6. 减小了32%bundle的体积
  7. Fiber架构:改变了之前react的组件渲染机制,新的架构使原来同步渲染的组件现在可 以异步化,可中途中断渲染,执行更高优先级的任务。释放浏览器主线程。
  8. react-call-return:16.1新增库。
  9. Fragement:16.2新增。可以通过Fragement直接返回多个组件。
    缺点:
    数组里的子节点必须要用逗号分离
    数组里的子节点必须要带key防止waring
    string类型要用双引号括住
  10. 新的生命周期函数:16.3新增;
  11. context API: context就是可以使用全局的变量,滥用会降低复用性。只想避免需要传很多 次props的话,可以直接使用component composition(就是通过props 自己传给指定的)
  12. createRef API:用于操作focus, text 选择,media playback
    触发即时动画
    与第三方组件结合
    函数组件里可以使用refs 但是不能把ref属性给它本身。
  13. forwardRef API:父组件需要将自己的引用传给子组件
  14. strictMode component:严格模式用来帮助开发者发现潜在问题的工具。就像Fragment 一样,它不会render任何的DOM 元素。
    识别出使用不安全生命周期的组件
    对使用string ref进行告警
    对使用findDOMNode进行告警
    探测某些产生副作用的方法
    对使用弃用context API进行警告
    15.memo():缓存函数组件以减少一些不必要的渲染
    16.Hooks

9.Hooks

React 中通常使用类定义或者函数定义创建组件:在类定义中,我们可以使用到许多 React 特性,例如 state、各种组件生命周期钩子等,但是在函数定义中,我们却无能为力,因此 React 16.8 版本推出了一个新功能 (React Hooks),通过它,可以更好的在函数定义组件中使用 React 特性。

  • 重要钩子
  1. 状态钩子 (useState): 用于定义组件的 State,其到类定义中this.state的功能;
// useState 只接受一个参数: 初始状态
// 返回的是组件名和更改该组件对应的函数
const [flag, setFlag] = useState(true);
// 修改状态
setFlag(false)
// 上面的代码映射到类定义中:
this.state = {
	flag: true	
}
const flag = this.state.flag
const setFlag = (bool) => {
    this.setState({
        flag: bool,
    })
}
  1. 生命周期钩子 (useEffect):
    useEffect(callback, [source])接受两个参数
    callback: 钩子回调函数;
    source: 设置触发条件,仅当 source 发生改变时才会触发;
    useEffect钩子在没有传入[source]参数时,默认在每次 render 时都会优先调用上次保存的回调中返回的函数,后再重新调用回调;
    通过第二个参数,我们便可模拟出几个常用的生命周期:
//componentDidMount: 传入[]时,就只会在初始化时调用一次
const useMount = (fn) => useEffect(fn, [])
//componentWillUnmount: 传入[],回调中的返回的函数也只会被最终执行一次
const useUnmount = (fn) => useEffect(() => fn, [])
//mounted: 可以使用 useState 封装成一个高度可复用的 mounted 状态;
const useMounted = () => {
    const [mounted, setMounted] = useState(false);
    useEffect(() => {
        !mounted && setMounted(true);
        return () => setMounted(false);
    }, []);
    return mounted;
}
//componentDidUpdate: useEffect每次均会执行,其实就是排除了 DidMount 后即可;
const mounted = useMounted() 
useEffect(() => {
    mounted && fn()
})
  1. useMemo 和 useCallback
    useCallback: 缓存回调函数,避免传入的回调每次都是新的函数实例而导致依赖组件重新渲染,具有性能优化的效果;
    useMemo: 用于缓存传入的 props,避免依赖的组件每次都重新渲染;
    useMemo 和 useCallback 接收的参数都是一样,第一个参数为回调 第二个参数为要依赖的数据
    共同作用
    1.仅仅 依赖数据 发生变化, 才会重新计算结果,也就是起到缓存的作用。
    两者区别
    1.useMemo 计算结果是 return 回来的值, 主要用于 缓存计算结果的值 ,应用场景如: 需要 计算的状态
    2.useCallback 计算结果是 函数, 主要用于 缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化 整个组件 都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费。
  2. 其它内置钩子:
    4.1 useContext: 获取 context 对象
    4.2 useReducer: 类似于 Redux 思想的实现,但其并不足以替代 Redux,可以理解成一个组件内部的 redux:
    并不是持久化存储,会随着组件被销毁而销毁;
    属于组件内部,各个组件是相互隔离的,单纯用它并无法共享数据;
    配合useContext`的全局性,可以完成一个轻量级的 Redux;(easy-peasy)
    4.3 useRef: 获取组件的真实节点;
    4.4 useLayoutEffect
    DOM更新同步钩子。用法与useEffect类似,只是区别于执行时间点的不同
    useEffect属于异步执行,并不会等待 DOM 真正渲染后执行,而useLayoutEffect则会真正渲染后才触发;
    可以获取更新后的 state;
  3. 自定义钩子(useXxxxx): 基于 Hooks 可以引用其它Hooks这个特性,我们可以编写自定义钩子
  • 好处:
    跨组件复用: 其实 render props / HOC 也是为了复用,相比于它们,Hooks 作为官方的底层 API,最为轻量,而且改造成本小,不会影响原来的组件层次结构和传说中的嵌套地狱;
    类定义更为复杂
    1.不同的生命周期会使逻辑变得分散且混乱,不易维护和管理;
    2.时刻需要关注this的指向问题;
    3.代码复用代价高,高阶组件的使用经常会使整个组件树变得臃肿;
    状态与UI隔离: 正是由于 Hooks 的特性,状态逻辑会变成更小的粒度,并且极容易被抽象成一个自定义 Hooks,组件中的状态和 UI 变得更为清晰和隔离。
  • 注意:
    1.避免在循环/条件判断/嵌套函数 中调用 hooks,保证调用顺序的稳定;
    2.只有函数定义组件 和 hooks 可以调用 hooks,避免在 类组件 或者 普通函数 中调用;
    3.不能在useEffect中使用useState,React 会报错提示;
    4.类组件不会被替换或废弃,不需要强制改造类组件,两种方式能并存;
  • React Hooks 如何保存状态
    React Hooks保存状态的位置其实与类组件的一致.
    两者的状态值都被挂载在组件实例对象FiberNode的memoizedState属性中。
    但是两者保存状态值的数据结构完全不同;类组件是直接把 state 属性中挂载的这 个开发者自定义的对象给保存到memoizedState属性中;而 React Hooks 是用链表来保存状态的,memoizedState属性保存的实际上是这个链表的头指针。
  • React Hooks 如何更新状态
    由 useState 返回的用来更新状态的函数(称为 dispatcher),当我们在每次调用 dispatcher 时,并不会立刻对状态值进行修改(状态值的更新是异步的),而是创建一条修改操作——在对应 Hook 对象的queue属性挂载的链表上加一个新节点。下次执行函数组件,再次调用 useState 时, React 才会根据每个 Hook 上挂载的更新操作链表来计算最新的状态值。

10.React和vue的区别和使用场景

  1. 数据是不是可变的:
    react是整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,所以在react中,是单向数据流,推崇结合immutable来实现数据不可变。
    vue的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立Watcher来监听,当属性变化的时候,响应式的更新对应的虚拟dom。
  2. React通过js来操作一切,vue是把html,css,js组合到一起用各自的处理方式
  3. 类式的组件写法,还是声明式的写法
    React是类式组件写法(16后转向函数式)
    vue是声明式的写法,通过传入各种options,api和参数都很多。
    所以react结合typescript更容易一起写,vue稍微复杂。
  • React 配合严格的 Flux 架构,适合超大规模多人协作的复杂项目。理论上 Vue 配合类似架构也可以胜任这样的用例,但缺少类似 Flux 这样的官方架构。小快灵的项目上,Vue 和 React 的选择更多是开发风格的偏好。对于需要对 DOM 进行很多自定义操作的项目,Vue 的灵活性优于 React。
    react的性能优化需要手动去做,而vue的性能优化是自动的,但是vue的响应式机制也有问题,就是当state特别多的时候,Watcher也会很多,会导致卡顿,所以大型应用(状态特别多的)一般用react,更加可控。

11.React的不可变值理念

  • 简化复杂的功能
    不可变性使得复杂的特性更容易实现。
  • 跟踪数据的改变
    如果直接修改数据,那么就很难跟踪到数据的改变。跟踪数据的改变需要可变对象可以 与改变之前的版本进行对比,这样整个对象树都需要被遍历一次。
    跟踪不可变数据的变化相对来说就容易多了。如果发现对象变成了一个新对象,那么 我们就可以说对象发生改变了。
  • 确定在 React 中何时重新渲染
    不可变性最主要的优势在于它可以帮助我们在 React 中创建 pure components。我们可 以很轻松的确定不可变数据是否发生了改变,从而确定何时对组件进行重新渲染。

12.Vuex与redux

  • Redux三大原则:
  1. 单一数据源:整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
  2. State 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
  3. 使用纯函数来执行修改:为了描述 action 如何改变 state tree ,你需要编写 reducers。

Redux数据流的顺序是:
View调用store.dispatch发起Action->store接受Action(action传入reducer函数,reducer 函数返回一个新的state)->通知store.subscribe订阅的重新渲染函数

Vuex是专门为Vue设计的状态管理框架,同样基于Flux架构,并吸收了Redux的优点
Vuex相对于Redux的不同点有:
1.改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,
无需switch,只需在对应的mutation函数里改变state值即可
2.由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可
3.Vuex数据流的顺序是:View调用store.commit提交对应的请求到Store中对应的 mutation函数->store改变(vue检测到数据变化自动渲染)

13.Redux相对于flux的改进

  • 改进
  1. 把store和Dispatcher合并,结构更加简单清晰
  2. 新增state角色,代表每个时间点store对应的值,对状态的管理更加明确
  • Redux缺点:
  1. 一个组件所需要的数据,必须由父组件传过来,而不能像flux中直接从store取。
  2. 当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新render,可能会有效率影响,或者需要写复杂的shouldComponentUpdate进行判断。

14.Diff算法

React的思路是递增法。通过对比新的列表中的节点,在原本的列表中的位置是否是递增,来判断当前节点是否需要移动。

  1. 把树形结构按照层级分解,只比较同级元素。
  2. 给列表结构的每个单元添加唯一的key属性,方便比较。
  3. React 只会匹配相同 class 的 component(这里面的class指的是组件的名字)
  4. 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 - dirty.到每一个 事件循环结束, React 检查所有标记 dirty的 component重新绘制.
  5. 选择性子树渲染。开发人员可以重写shouldComponentUpdate提高diff的性能

15.JSX

jsx是JavaScript的一种语法扩展,它跟模板语言很接近,但是它充分具备JavaScript的能力。JSX的定位是JavaScript的扩展,直接决定了浏览器并不会像天然JavaScript一样地支持JSX,需要通过babel转译:JSX会被babel编译为:React.createElement(),React.createElement()将返回一个叫作“ReactElement”的JS对象。
JSX语法糖允许前端开发者使用我们最熟悉的类HTML标签语法来创建虚拟DOM,提升了研发效率与研发体验。

  • 如何使用JSX
  1. 可以在大括号内放置任何有效的 JavaScript 表达式。
  2. 在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。
    可以通过使用引号,来将属性值指定为字符串字面量
    可以使用大括号,来在属性值中插入一个 JavaScript 表达式
  3. 因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。
  4. JSX中的标签可以是单标签,也可以是双标签,但必须保证标签是闭合的。

16.MVC和MVVM

MVC (Model View Controller)

MVC 模式将程序分为三个部分:模型(Model)、视图(View)、控制器(Controller)。
Model 模型层: 业务数据的处理和存储,数据更新后更新;
View 视图层: 人机交互接口,一般为展示给用户的界面;
Controller 控制器层 : 负责连接 Model 层和 View 层,接受并处理 View 层触发的事件,并在 Model 层的数据状态变动时更新 View 层;
MVC 模式的目的是通过引入 Controller 层来将 Model 层和 View 层分离,使得系统在可维护性和可读性上有了进步。
典型思路是 View 层通过事件通知到 Controller 层,Controller 层经过对事件的处理完成相关业务逻辑,要求 Model 层改变数据状态,Model 层再将新数据更新到 View层。示意图如下:
在这里插入图片描述

MVVM

MVVM 模式将程序分为三个部分:模型(Model)、视图(View)、视图模型(View-Model)。
Model 层和 View 层也被隔离开,彻底解耦,ViewModel 层负责绑定 Model 层和 View 层,ViewModel 层和 View 层采用双向绑定的形式。
在这里插入图片描述

七、综合开发

1.对重构的理解

  • 网站重构:
    在不改变外部行为的前提下,简化结构、添加可读性,而在网站前端保持一致的行为。也就是说是在不改变UI的情况下,对网站进行优化,在扩展的同时保持一致的UI
  • 对于传统的网站来说重构通常是:
    表格(table)布局改为DIV+CSS
    使网站前端兼容于现代浏览器(针对于不合规范的CSS、如对IE6有效的)
    对于移动平台的优化
    针对于SEO进行优化

2.好的前端代码

  • 高复用低耦合,这样文件小,好维护,而且好扩展。
  • 具有可用性、健壮性、可靠性、宽容性等特点
  • 遵循设计模式的六大原则

3.设计六大原则

  1. 单一原则:一个类或者方法只负责一项职责,尽量做到类的只有一个行为原因引起变化;
  2. 里氏替换原则:子类可以扩展父类的功能,但不能改变原有父类的功能;
  3. 依赖倒置原则:面向接口编程;传变量或者参数,尽量使用抽象类,或者接口;
  4. 接口隔离:建立单一接口;复杂的接口,根据业务拆分成多个简单接口
  5. 迪米特原则:最少知道原则,尽量降低类与类之间的耦合;
  6. 开闭原则:用抽象构建架构,用实现扩展原则;

4.对前端工程师的理解

  • 前端是最贴近用户的程序员,比后端、数据库、产品经理、运营、安全都近
  • 实现界面交互
  • 提升用户体验
  • 基于NodeJS,可跨平台开发
  • 前端的能力就是能让产品从 90分进化到 100 分,甚至更好,
  • 与团队成员,UI设计,产品经理的沟通;
  • 做好的页面结构,页面重构和用户体验;

5.你觉得前端工程的价值体现在哪

为简化用户使用提供技术支持(交互部分)
为多个浏览器兼容性提供支持
为提高用户浏览速度(浏览器性能)提供支持
为跨平台或者其他基于webkit或其他渲染引擎的应用提供支持
为展示数据提供支持(数据接口)

6.组件封装

目的:为了重用,提高开发效率和代码质量
注意:低耦合,单一职责,可复用性,可维护性

7.性能优化

  • 利用缓存
    对于静态资源文件实现强缓存和协商缓存
    对于不经常更新的接口数据采用本地存储做数据缓存
  • DNS优化
    分服务器部署,增加HTTP并发性(导致DNS解析变慢)
    DNS Prefetch
  • TCP的三次握手和四次挥手
    Connection:keep-alive
  • 数据传输
  1. 减少数据传输的大小
    内容或者数据压缩(webpack等)
    服务器端一定要开启GZIP压缩(一般能压缩60%左右)
    大批量数据分批次请求(例如:下拉刷新或者分页,保证首次加载请求数据少)
  2. 减少HTTP请求的次数
    资源文件合并处理
    字体图标
    雪碧图 CSS-Sprit
    图片的BASE64
  • CDN服务器“地域分布式”
  • 采用HTTP2.0

8.服务端渲染

SSR是Server Side Render简称;页面上的内容是通过服务端渲染生成的,浏览器直接显示服务端返回的html就可以了。
服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。

  • 服务端渲染的优点:
    1、尽量不占用前端的资源,前端这块耗时少,速度快。
    2、有利于SEO优化,因为在后端有完整的html页面,所以爬虫更容易爬取信息。

  • 服务端渲染的缺点:
    1、不利于前后端分离,开发的效率降低了。
    2、对html的解析,对前端来说加快了速度,但是加大了服务器的压力。

  • 服务端和客户端渲染的区别:
    1、二者本质的区别:是谁来完成了html的完整拼接,服务端渲染是在服务端生成DOM树,客户端渲染是在客户端生成DOM树。
    2、响应速度:服务端渲染会加快页面的响应速度,客户端渲染页面的响应速度慢。
    3、SEO优化:服务端渲染因为是多个页面,更有利于爬虫爬取信息,客户端渲染不利于SEO优化。
    4、开发效率:服务端渲染逻辑分离的不好,不利于前后端分离,开发效率低,客户端渲染是采用前后端分离的方式开发,效率更高,也是大部分业务采取的渲染方式。

  • 渲染方式选择
    如果是企业级网站,主要功能是页面展示,它没有复杂的交互,并且需要良好的SEO,那我们应该使用服务端渲染。
    如果是后台管理页面,交互性很强,它不需要考虑到SEO,那我们应该使用客户端渲染。
    具体使用哪种渲染方式也不是绝对的,现在很多网站使用服务端渲染和客户端渲染结合的方式:首屏使用服务端渲染,其他页面使用客户端渲染。这样可以保证首屏的加载速度,也完成了前后端分离。

9.GIT命令

  1. 常用的 Git 命令
    首先,通过git clone <项目远程地址>下载下来最新的代码,例如git clone git@git.coding.net:username/project-name.git,默认会下载master分支。
    然后修改代码,修改过程中可以通过git status看到自己的修改情况,通过git diff <文件名>可查阅单个文件的差异。
    最后,将修改的内容提交到远程服务器,做如下操作

git add .
git commit -m "xxx"
git push origin master
如果别人也提交了代码,你想同步别人提交的内容,执行git pull origin master即可。

  1. 如何多人协作开发
    多人协作开发,就不能使用master分支了,而是要每个开发者单独拉一个分支,使用git checkout -b <branchname>,运行git branch可以看到本地所有的分支名称。
    自己的分支,如果想同步master分支的内容,可运行git merge master。切换分支可使用git checkout <branchname>
    在自己的分支上修改了内容,可以将自己的分支提交到远程服务器

git add .
git commit -m "xxx"
git push origin <branchname>

最后,待代码测试没问题,再将自己分支的内容合并到master分支,然后提交到远程服务器。

git checkout master
git merge <branchname>
git push origin master

10.前端新技术

微前端

一种由独立交付的多个前端应用组成整体的架构风格。具体的,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的小块,而在用户看来仍然是内聚的单个产品
将庞大的整体拆成可控的小块,并明确它们之间的依赖关系。关键优势在于:
代码库更小,更内聚、可维护性更高
松耦合、自治的团队可扩展性更好
渐进地升级、更新甚至重写部分前端功能成为了可能

Serverless

一种 “无服务器架构”,无需关心程序运行环境、资源及数量,只要将精力 、集中到业务逻辑上的技术。
会改变前后端接口定义规范。
一定会改变前后端联调方式,让前端参与服务器逻辑开发,甚至 Node Java 混部。
大大降低 Nodejs 服务器维护门槛,只要会写 JS 代码就可以维护 Node 服务,而无需学习 DevOps 相关知识。

Flutter

Flutter的优点非常明显,如果你选择一个跨平台框架,与众多基于html的跨平台框架相比,Flutter绝对是体验最好,性能与构建思路几乎最接近原生开发的框架。

  • 性能强大,流畅
    Flutter对比weex和react native相比,性能的强大是有目共睹的。基于dom树渲染原生组件,很难与直接在原生视图上绘图比肩性能,Google作为一个轮子大厂,直接在两个平台上重写了各自的UIKit,对接到平台底层,减少UI层的多层转换,UI性能可以比肩原生,这个优势在滑动和播放动画时尤为明显。
  • 路由设计优秀
    Flutter的路由传值非常方便,push一个路由,会返回一个Future对象(也就是Promise对象),使用await或者.then就可以在目标路由pop,回到当前页面时收到返回值。这个反向传值的设计基本是甩了微信小程序一条街了。弹出dialog等一些操作也是使用的路由方法,几乎不用担心出现传值困难
  • 单例模式
    Flutter支持单例模式,单例模式的实现也非常简单。单例模式很好的解决了一些问题。相比之下,js的单例则并不是一个真正的单例,或者说不是一个简单的单例,这也是受限于js所运行的环境。单例模式并不总是合理的,容易被滥用。但是在App的初期开发中,往往一个容易实现的单例可以帮助我们快速完成一些逻辑的搭建。
  • 优秀的动画设计
    Flutter的动画简单到不可思议,动画对象会根据屏幕刷新率每秒产生很多个(一般是60个)浮点数,只需要将一个组件属性通过部件(Tween)关联到动画对象上,Flutter会确保在每一帧渲染正确的组件,从而形成连贯的动画。这种十分暴力的操作在Flutter上却看不到明显的卡顿,这也是Flutter的一个魔力所在。相比之下其他跨平台框架几乎不能设计动画……往往会遭遇非常严重的性能问题。
  • UI跨平台稳定
    Google直接在两个平台上在底层重写了UIKit,不依赖于Css等外部解释器,几乎不存在UI表达不理想,渲染不正常的情况,可以获得非常稳定的UI表达效果。Css换个浏览器就有不同的表现,基于Css的跨平台框架很难获得稳定的UI表现。
  • 可选静态的语言,语言特性优秀
    Dart是一个静态语言,这也是相对于js的一个优势。Dart可以被编译成js,但是看起来更像java。静态语言可以避免错误,获得更多的编辑器提示词,极大的增加可维护性。很多js库也已经用ts重写了,Vue3.0的底层也将全部使用ts编写,静态语言的优势不言而喻。

缺点

  • 假装跨平台,躲不开原生代码
    这是最大的问题,跨平台框架说白了就是UI跨平台,最后还是在原生平台运行,本来两个平台就有天壤之别,一套代码就想吃掉iOS和Android在实际应用之中其实根本就不现实。Flutter具有与原生代码互相调用的能力固然非常科学,但是问题反而显得更加明显——我一个前端工程师上哪里去知道什么是UIViewController,什么是Activity呢?我要是双端都熟悉,学习Flutter就显得很没有必要。这是一个很矛盾的点,特别是在团队里,只有几个前端突然想学Flutter,是绝对做不来大项目的,如果有原生开发者,那就没必要搞Flutter了。
  • 组合而不是继承的思路
    Flutter提倡“组合”,而不是“继承”。在iOS开发中,我们经常会继承UIView,重写UIView的某个生命周期函数,再添加一些方法和属性,来完成一个自定义的View。但是在Flutter中这些都是不可能的——属性都是final的,例如你继承了了一个Container,你是不能在它的生命周期中修改他的属性的。你始终需要嵌套组合几种Widget,例如Row,Container,ListView等Widget。这种方法非常不符合直觉,初学时很难想明白如何构建一个完整的组件。
  • Widget的类型难以选择
    Flutter的Widget分为StatefulWidget和StatelessWidget两种,一种是带状态的一种是不带状态的,刚开发的时候很难想明白用哪个,因为StatelessWidget也能存值,其实区别就在于框架重构UI的时候会使用State来重构,如果是StatelessWidget,暂时存进去的值就没了。但是问题远不止这么简单,好在只是有点麻烦,并不影响产品性能。
  • 糟糕的UI控件API
    虽然google尽可能的让我们通过构造函数定制化Widget,但是也难免有遗漏的。例如,又一次我想修改一个Appbar的高度,居然没有找到关于高度的属性,通过阅读源码发现,高度是写死(const)的。上文已经说过,无法通过生命周期来改变组件属性,自己写Appbar显得非常没必要,毕竟我还是想使用Appbar的各种方便的功能。最后我只能把他的源码全部复制出来,直接修改高度来使用。初学框架,和一些初级开发者是不可能有迅速阅读源码的能力的(作为框架也不应该产生如此问题)。一些定制化的UI的Api设计经常有缺失,好在我已经基本习惯了。除了Appbar这种复杂的组件,自己写一个小组件也并不费事。
  • 糟糕的资源管理设计
    这里是最蠢的,Flutter支持动态加载不同分辨率的图片,但是目录设计太鬼畜了。简单的说,Sketch导出的多分辨率资源,几乎不可能直接拖到Flutter里用,极其,极其,麻烦。

snowpack

  • snowpack 的优点:
    全量构建快,增量构建也快。不需要打包,减少 CPU 消耗和等待时间,提升开发体验;
    源码对于打包工具的依赖比较低,在 webpack 的项目,可能会因使用 import 语法导入各种非 JS 资源和依赖 plugin 处理代码导致比较难替换了;
    依赖模块和源码间相互独立,相当于直接做了 code spliting,可以高效利用缓存来提高性能,项目重新发布时也不用让用户重新获取整个包。
  • 终归是有缺点的:
    把传统的 bundle 文件拆分成一堆小文件,多请求难免受制于浏览器 HTTP 请求并发和并行的瓶颈;
    没有生态,相关的工具链很缺;
    当前还有很多 npm 模块并没有提供 ESM 格式的导出文件,这也是最致命的。

八、js基本api

array

  • 改变自身
    copyWithin(待取代位置, 取代位置开始,取代位置结束);
    splice(位置,删除个数,新增个数);
    pop() 返回删除元素
    push(插入元素) 返回长度
    unshift(插入元素)插入 返回长度
    shift() 头部删除 返回删除元素
    fill(填充值, 开始,结束);
    reverse() 倒序
    sort() 排序

  • 不改变
    slice(开始,结束)截取
    indexOf(字符) 查询指定字符 不存在返回-1
    lastIndexOf(字符) 倒序查询
    concat(连接数字或数组)
    join(符号) 数组转字符串
    includes(字符) 有返回true

string

  • slice(start,end);截取字符串时不包括下标为end的元素,没有end时默认从start到结束的所有字符串。
  • substr(start,length);第二个参数是子串中的字符数,必须是数值。
  • substring(start,end);返回的字符串中 不包括 end处的字符。如果 start 比 end 大,那么该方法在提取子串之前会先交换这两个参数。
  • charAt(pos);返回指定位置(如上pos)的字符。如果pos小于0或者大于等于字符串的长度string.length,它会返回空字符串。
  • indexOf(searchString,position);searchString。如果它被找到,就返回第1个匹配字符的位置,否则返回-1。可选参数position可设置从string的某个指定的位置开始查找。
  • lastIndexOf(searchString,position)从该字符串的末尾开始查找而不是从开头。
  • match(regexp);让字符串和一个正则表达式进行匹配。它依据g标识来决定如何进行匹配。如果没有g标识,那么调用string.match(regexp)的结果与调用regexp.exec(string)的结果相同。带g标识返回的是一个结果数组
  • replace(searchValue,replaceValue)
    作用:replace方法对string进行查找和替换操作,并返回一个新的字符串。
    取值:而参数searchValue可以是一个字符串或者一个正则表达式对象。
    第一种情况:如果searchValue是一个字符串,那么searchValue只会在第1次出现 的地方被替换

map

var m = new Map(); // 空Map

m.set('Adam', 67); // 添加新的key-value

m.set('Bob', 59);

m.has('Adam'); // 是否存在key 'Adam': true

m.get('Adam'); // 67

m.delete('Adam'); // 删除key 'Adam'

m.get('Adam'); // undefined
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值