已同步到掘金,掘金地址: https://juejin.cn/post/7075332630417244173
个人整理了很多网上常见的面试题,希望也能通过这来复习
内容有点多,可能CSDN上预览效果不好,想要markdown文档的可以私信我,推荐使用Typora
看
比较好的面试题
2021 年我的前端面试准备
2021年前端面试必读文章【超三百篇文章/赠复习导图】
HTML 语义化简单来说就是用正确的标签来做正确的事。
比如表示段落用 p 标签、表示标题用 h1-h6 标签、表示文章就用 article 等。
Doctype作用?严格模式与混杂模式如何区分?它们有何差异?
声明位于文档中的最前面,处于
标签之前。告知浏览器以何种模式来渲染文档。
严格模式的排版和 JS 运作模式是 以该浏览器支持的最高标准运行。
在混杂模式中,页面以宽松的向后兼容的方式显示。模拟老式浏览器的行为以防止站 点无法工作。
DOCTYPE 不存在或格式不正确会导致文档以混杂模式呈现。复制代码 你知道多少种 Doctype 文档类型? 该标签可声明三种 DTD 类型,分别表示严格版本、过渡版本以及基于框架的 HTML 文档。 HTML 4.01 规定了三种文档类型:Strict、Transitional 以及 Frameset。 XHTML 1.0 规定了三种 XML 文档类型:Strict、Transitional 以及 Frameset。 Standards (标准)模式(也就是严格呈现模式)用于呈现遵循最新标准的网页,而 Quirks (包容)模式(也就是松散呈现模式或者兼容模式)用于呈现为传统浏览器而设计的网页。
可以通过 display 修改 inline-block
, block
, inline
注意
只有文字才能组成段落,因此 p
标签里面不能放块级元素,特别是 p
标签不能放 div
。同理还有这些标签h1,h2,h3,h4,h5,h6,dt
,他们都是文字类块级标签,里面不能放其他块级元素。
HTML5新特性
CSS3有哪些新特性?CSS3新特性详解
1、圆角效果;2、图形化边界;3、块阴影与文字阴影;4、使用 RGBA 实现透明效果;5、渐变效果;6、使用“@Font-Face”实现定制字体;7、多背景图;8、文字或图像的变形处理;9、多栏布局;10、媒体查询等。
1、颜色:新增RGBA、HSLA模式
2、文字阴影:(text-shadow)
3、边框:圆角(border-radius)边框阴影:box-shadow
4、盒子模型:box-sizing
5、背景:background-size,background-origin background-clip(削弱)
6、渐变:linear-gradient(线性渐变):
eg: background-image: linear-gradient(100deg, #237b9f, #f2febd);
radial-gradient (径向渐变)
7、过渡:transition可实现动画
8、自定义动画: animate@keyfrom
9、媒体查询:多栏布局@media screen and (width:800px)
10、border-image
11、2D转换:transform:translate(x,y) rotate(x,y)旋转 skew(x,y)倾斜 scale(x,y)缩放
12、3D转换
13、字体图标:Font-Face
14、弹性布局:flex
id 选择器( #myid)
类选择器(.myclassname)
标签选择器(div, h1, p)相邻选择器(h1 + p)
子选择器(ul > li)后代选择器(li a)
属性选择器(a[rel = “external”])
伪类选择器(a: hover, li:nth-child)
通配符选择器( * )
!Important > 行内式 > id > 类/伪类/属性 > 标签选择器 > 全局
(对应权重:无穷大∞ > 1000> 100 > 10 > 1 > 0)
一个盒子,会用 content,padding,border,margin 四部分,
标准的盒模型的宽高指的是 content 部分
ie 的盒模型的宽高包括了 content+padding+border
我们可以通过 box-sizing 修改盒模型,box-sizing border-box
content-box
在垂直方向上的两个盒子,他们的 margin 会发生合并(会取最大的值),比如上边盒子设置margin-bottom:20px
,下边盒子设置margin-top:30px;
,那么两个盒子间的间距只有30px
,不会是50px
解决 margin 合并,我们可以给其中一个盒子套上一个父盒子,给父盒子设置 BFC
效果: 一个父盒子中有一个子盒子,我们给子盒子设置margin-top:xxpx
结果发现会带着父盒子一起移动(就效果和父盒子设置margin-top:xxpx
的效果一样)
解决: 1、给父盒子设置 border,例如设置border:1px solid red;
2、给父盒子设置 BFC
块级格式化上下文 (block format context)
BFC 的布局规则 *
触发 BFC 的条件 *
解决什么问题
可以用来解决两栏布局BFC 的区域不会与 float box 重叠
.left { float: flet; }
.right { overflow: hidden; }
解决margin塌陷和margin合并问题
解决浮动元素无法撑起父元素
设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效
rem是一个相对单位,rem的是相对于html元素的字体大小,没有继承性
em是一个相对单位,是相对于父元素字体大小有继承性
px是一个“绝对单位”,就是css中定义的像素,利用px设置字体大小及元素的宽高等,比较稳定和精确。
响应式布局有哪些实现方式?什么是响应式设计?响应式设计的基本原理是什么?
1.百分比布局,但是无法对字体,边框等比例缩放
2.弹性盒子布局 display:flex
3.rem布局,1rem=html的font-size值的大小
- css3媒体查询 @media screen and(max-width: 750px){}
5.vw+vh
6.使用一些框架(bootstrap,vant)
什么是响应式设计:响应式网站设计是一个网站能够兼容多个终端,智能地根据不同设备环境进行相对应的布局
响应式设计的基本原理:基本原理是通过媒体查询检测不同的设备屏幕尺寸设置不同的css样式 页面头部必须有meta声明的
方法一:给父元素设置成弹性盒子,子元素横向居中,纵向居中
方法二:父相子绝后,子部分向上移动本身宽度和高度的一半,也可以用transfrom:translate(-50%,-50%)(最常用方法)
方法三:父相子绝,子元素所有定位为0,margin设置auto自适应
DOM0 级模型、IE 事件模型、DOM2 级事件模型
就比如用户触发一个点击事件,有一个触发的过程
事件捕获-阶段(从上大小,从外到内)—>处于目标事件-阶段---->事件冒泡-阶段(从下到上,从内到外)
window.addEventListener(
"click",
function (event) {
event = event || window.event /*ie*/;
const target = event.target || event.srcElement; /*ie*/ // 拿到事件目标
// 阻止冒泡
// event.stopPropagation()
// event.cancelBubble=true; // ie
// 阻止默认事件
// event.preventDefault();
// event.returnValue=false; // ie
},
/* 是否使用捕获,默认是fasle, */ fasle
);
简介:事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是
在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的
触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。
举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用
事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事
件触发机制
8 中, ES6
出的 Symbol BigInt
Number String Boolean undefined null Object Symbol BigInt
js 的基本数据类型和复杂数据类型的区别(在堆和栈中,赋值时的不同,一个拷贝值一个拷贝地址)
基本类型和引用类型在内存上存储的区别
null 与 undefined 的异同
相同点:
不同点:
null 转换成数字是 0, undefined 转换数字是NaN
undefined 代表的含义是未定义, null 代表的含义是空对象。
typeof null 返回’object’,typeof undefined 返回’undefined’
null == undefined; // true
null === undefined; // false
复制代码;
- 其实 null 不是对象,虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。
**说说 JavaScript 中判断数据类型的几种方法**
**typeof**
- `typeof`一般用来判断基本数据类型,**除了判断 null 会输出"object",其它都是正确的**
- `typeof`判断引用数据类型时,**除了判断函数会输出"function",其它都是输出"object"**
**instanceof**
> Instanceof 可以准确的判断引用数据类型,它的原理是检测构造函数的`prototype`属性是否在某个实例对象的原型链上, 不能判断基本数据类型
```js
// instanceof 的实现
function instanceofOper(left, right) {
const prototype = right.prototype;
while (left) {
if ((left = left.__proto__) === prototype) {
return true;
}
}
return false;
}
// let obj = {}
// Object.getPrototypeOf(obj) === obj.__proto__ ==> true
// 实现 instanceof 2
function myInstanceof(left, right) {
// 这里先用typeof来判断基础数据类型,如果是,直接返回false
if (typeof left !== "object" || left === null) return false;
// getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
let proto = Object.getPrototypeOf(left);
while (true) {
if (proto === null) return false;
if (proto === right.prototype) return true; //找到相同原型对象,返回true
proto = Object.getPrototypeof(proto);
}
}
Object.prototype.toString.call() 返回 [object Xxxx]
都能判断
let obj = { b: "xxx" };
let arr = [{ a: "ss" }, obj, 333];
// 赋值
let arr2 = arr;
// 浅拷贝-只拷贝了一层,深层次的引用还是存在
// Object.assign(), ...扩展运算符,slice等
let arr2 = arr.slice();
let arr2 = [...arr];
obj.b = "222"; // arr2[1].b => 222
// arr[2] = 4444 ==> arr2[2] ===> 333
// 深拷贝
// 最简单的
let arr2 = JSON.parse(JSON.stringify(arr));
// undefined 丢失,如果里面有 函数,还有循环引用就会报错
// 自己封装,要递归处理
实现深拷贝-简单版
export function deepClone(obj, map = new Map()) {
if (!obj && typeof obj !== 'object') return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags)
if (map.get(obj)) { // 如果有循环引用、就返回这个对象
return map.get(obj)
}
const cloneObj = obj.constructor() // 数组的就是[],对象就是{}
map.set(obj, cloneObj) // 缓存对象,用于循环引用的情况
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], map)
}
}
return cloneObj
}
commonjs
// 由nodejs实现
const fs = require("fs");
module.exports = {};
ESM
// 由es6实现
import $ from "jquery";
export default $;
AMD(异步加载模块)
// 由RequireJS实现
define(["juqery", "vue"], function ($, Vue) {
// 依赖必须一开始就写好
$("#app");
new Vue({});
});
CMD
// 由SeaJS 实现
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// ....
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
})
UMD (通用加载模块)
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Vue = factory());
}(this, function () { 'use strict';
https://blog.csdn.net/qq_38912819/article/details/80597101
总结:CommonJS 是值拷贝,ES6是值的引用
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import
有点像 Unix 系统的“符号连接”,原始值变了,import
加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
export
命令显式指定输出的代码,import
时采用静态命令的形式。即在import
时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
JavaScript 会阻塞 DOM 的解析,因此也就会阻塞 DOM 的加载。所以有时候我们希望延迟 JS 的加载来提高页面的加载速度。
call,apply, bind 都是改变 this 指向,bind 不会立即执行,会返回的是一个绑定 this 的新函数
面试官问:能否模拟实现 JS 的 call 和 apply 方法
obj.call(this指向, 参数1, 参数2)ss
obj.apply(this指向, [参数1, 参数2])
function fn(age) {
console.log(this, age)
}
const obj = {name:''}
const result = fn.bind(obj) // bind会返回一个新的函数
result(20)
// 实现一个 apply
Function.prototype.myApply = function (context) {
context = context || window;
const key = Symbol();
context[key] = this;
var res = context[key](...arguments[1]);
delete context[key];
return res;
};
debounce 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
function debounce(func, wait, immediate) {
let timeout;
return function () {
const context = this;
const args = [...arguments];
if (timeout) clearTimeout(timeout);
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(() => {
func.apply(context, args)
}, wait);
}
}
}
就是指连续触发事件但是在 n 秒中只执行一次函数
function throttle(fn, wait) {
let pre = 0;
return function (...args) {
let now = Date.now();
if (now - pre >= wait) {
fn.apply(this, args);
pre = now;
}
};
}
闭包是指有权访问另一个函数作用域中的变量的函数 ——《JavaScript 高级程序设计》
当函数可以记住并访问所在的词法作用域时,就产生了闭包,
即使函数是在当前词法作用域之外执行 ——《你不知道的 JavaScript》
原型: 对象中固有的__proto__
属性,该属性指向对象的prototype
原型属性。
原型链: 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是Object.prototype
所以这就是我们新建的对象为什么能够使用toString()
等方法的原因。
特点: JavaScript
对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
this
对象是是执行上下文中的一个属性,它指向最后一次调用这个方法的对象,在全局函数中,this
等于window
,而当函数被作为某个对象调用时,this 等于那个对象。 在实际开发中,this
的指向可以通过四种调用模式来判断。
this
指向全局对象。this
指向这个对象。this
指向这个用new
新创建的对象。apply 、 call 和 bind
调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply
接收参数的是数组,call
接受参数列表,`` bind方法通过传入一个对象,返回一个
this绑定了传入对象的新函数。这个函数的
this指向除了使用
new `时会被改变,其他情况下都不会改变。new
面试官问:能否模拟实现 JS 的 new 操作符
prototype
对象。this
指向这个对象,执行构造函数的代码(为这个新对象添加属性)// new 操作符的实现
function newOperator(ctor) {
if (typeof ctor !== "function") {
throw "newOperator function the first param must be a function";
}
newOperator.target = ctor;
var newObj = Object.create(ctor.prototype);
var argsArr = [].slice.call(arguments, 1);
var ctorReturnResult = ctor.apply(newObj, argsArr);
var isObject =
typeof ctorReturnResult === "object" && ctorReturnResult !== null;
var isFunction = typeof ctorReturnResult === "function";
if (isObject || isFunction) {
return ctorReturnResult;
}
return newObj;
}
作用域
负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。(全局作用域、函数作用域、块级作用域)。 作用域链就是从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链
。
function Animal(name) {
// 属性
this.name = name || "Animal";
// 实例方法
this.sleep = function () {
console.log(this.name + "正在睡觉!");
};
}
// 原型方法
Animal.prototype.eat = function (food) {
console.log(this.name + "正在吃:" + food);
};
(1)第一种是以原型链的方式来实现继承
,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。
// 原型链继承
function Cat() {}
Cat.prototype = new Animal("小黄"); // 缺点 无法实现多继承 来自原型对象的所有属性被所有实例共享
Cat.prototype.name = "cat";
(2)第二种方式是使用借用构造函数
的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。
// 借用构造函数继承
function Cat() {
Animal.call(this, "小黄");
// 缺点 只能继承父类实例的属性和方法,不能继承原型上的属性和方法。
}
(3)第三种方式是组合继承
,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。
(4)第四种方式是原型式继承
,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
(5)第五种方式是寄生式继承
,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。
function createAnother(original){
var clone = object(original);//通过调用object函数创建一个新对象
clone.sayHi = function(){//以某种方式来增强这个对象
alert("hi");
};
return clone;//返回这个对象
}
(6)第六种方式是寄生式组合继承
,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。
function extend(subClass,superClass){
var prototype = object(superClass.prototype);//创建对象
prototype.constructor = subClass;//增强对象
subClass.prototype = prototype;//指定对象
}
大家都知道 JS 中在使用运算符号或者对比符时,会自带隐式转换,规则如下:
-、*、/、% :一律转换成数值后计算
+:
数字 + 字符串 = 字符串, 运算顺序是从左到右
数字 + 对象, 优先调用对象的 valueOf -> toString
数字 + boolean/null -> 数字
数字 + undefined -> NaN
[1].toString() === ‘1’
{}.toString() === ‘[object object]’
NaN !== NaN 、+undefined 为 NaN
==
、===
的区别?==
会先进行类型转换再比较===
比较时不会进行类型转换,类型不同则直接返回 falseObject.is()
在===
基础上特别处理了NaN
,-0
,+0
,保证-0 与+0 不相等,但 NaN 与 NaN 相等==
操作符的强制类型转换规则${data}
);...
扩展运算符(数组、对象);;
const
声明一个只读的常量。一旦声明,常量的值就不能改变 https://es6.ruanyifeng.com/#docs/let
var 在全局作用域中声明的变量会变成全局变量
let、const 和 var 的区别
不允许重复声明
不存在变量提升
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
暂时性死区(不能在未声明之前使用)
注意暂时性死区和不存在变量提升不是同一个东西
只要块级作用域内存在let
命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123; // 声明了 tmp
if (true) {
tmp = "abc"; // ReferenceError
let tmp;
}
块级作用域:用 let 和 const 声明的变量,在这个块中会形成块级作用域
es5 只有函数作用域和全局作用域
IIFE 立即执行函数表达式
// IIFE 写法
(function () {
var tmp = ...;
...
}());
// 块级作用域写法
{
let tmp = ...;
...
}
// 函数声明
function a() {}
// 函数表达式
const b = function () {};
reset 参数
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3); // 10
扩展运算符
console.log(...[1, 2, 3]);
// 1 2 3
const b = { ...{ a: "2", b: "3" } };
?.
可选链运算符
左侧的对象是否为
null
或undefined
。如果是的,就不再往下运算,而是返回undefined
a?.b;
// 等同于
a == null ? undefined : a.b;
// 注意 undefined == null ==> true
??
Null 判断运算符
https://es6.ruanyifeng.com/#docs/operator#Null-%E5%88%A4%E6%96%AD%E8%BF%90%E7%AE%97%E7%AC%A6
const headerText = response.settings.headerText ?? "Hello, world!";
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;
但左侧的为 undefined
或者null
是就返回右边的,否则就直接返回左边的
this
,this
是继承于当前的上下文,不能通过call
,apply
,bind
去改变 thisarguments
对象,但是可以访问外围函数的 arguments
对象new
关键字调用(不能作为构造函数),同样也没有 new.target
和原型vue2 是通过Object.defineProperty
来实现响应式的,所以就会有一些缺陷
arr[index]='xxx'
)vue 不能做响应式处理可用下面的解决响应式
Vue.set ==> this.$set(对象\数组, key 值、index, value)
修改数组length
, 调用数据的 splice
方法
beforeCreate 实例化之前这里能拿到this,但是还不能拿到data里面的数据
created 实例化之后
beforeMount()
mounted() $el
beforeUpdate
updated
beforeDestroy 清除定时/移除监听事件
destroyed
// 被keep-alive 包裹的
// keep-alive 标签 include exclude max
activated() {},
deactivated() {},
// 父子
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted。
// 离开页面:实例销毁 --> DOM卸载
parent beforeDestroy
child beforeDestroy
child destroyed
parent destroyed
// component 动态组件,通过is设置要显示的组件
<component is="UserInfo" >
就是给组件设置name
,之后就可以在当前组件去递归使用组件
// Vue组件间的传值的几种方式
1. props/emit
2. $attrs/$listeners // $attrs 除了父级作用域 props、class、style 之外的属性
// $listeners 父组件里面的所有的监听方法
3. $refs/$parent/$children/$root/
4. vuex
5. 事件总线,通过new Vue去实现 / mitt <==> vue3
6. provide/inject
// 父组件
props: {},
provide() {
name: this.name,
user: this.user
}
// 子组件
props: {},
inject: ['user']
7. 本地存储、全局变量
export default {
name: "MyComponentName",
mixins: [tableMixin],
components: {},
inject: ["xxx"],
// props: ['value', 'visible'],
props: {
id: String,
type: {
// required: true,
type: String,
default: "warning",
validator(val) {
return ["primary", "warning", "danger", "success", "info"].includes(
val
);
},
},
list: {
type: Array,
default: () => [],
},
},
data() {
return {
name: "张三",
user: { name: "张三", age: 18 },
loading: true,
// vue2
obj: {
name: "李四~",
},
// vue2 会进行深度合并
// obj {"name":"李四~","age":19}
// vue3 { name: "李四~" }
};
},
// provide 不支持响应式,想支持响应式的话我们要传对象
provide() {
return {
userName: this.name,
user: this.user,
};
},
computed: {
// fullName() {
// return 'xxxxx'
// }
fullName: {
get() {
return this.$store.state.userName;
// return '李四'
},
set(val) {
this.$store.commit("SET_NAME", val);
},
},
},
watch: {
// name(value) {
// this.handlerName()
// }
// name: {
// immediate: true,
// deep: true, //
// handler(val, oldValue) {
// this.handlerName()
// },
// },
// this.obj.name = 'xxxx' 这样不会执行
// this.obj = {name: 'xxx'} 这样才会执行
// obj(value) {
// console.log(' value: ', value)
// }
// 和上面等价
// obj: {
// handler(value) {
// console.log(" value: ", value)
// },
// },
// this.obj.name = 'xxxx' 这样去修改也能监听
// obj: {
// deep: true, // 深度监听
// immediate: true, // 第一次就用执行这个方法,可以理解为在 created 的时候会执行 handler
// handler(value) {
// console.log(" value: ", value)
// },
// },
//
// obj: {
// deep: true, // 深度监听
// immediate: true, // 第一次就用执行这个方法,可以理解为在 created 的时候会执行 handler
// handler: 'handlerName',
// },
// ==》
// obj: 'handlerName'
// '$route.path': {},
// 'obj.a' : {}
},
beforeCreate() {
console.log("this", this);
},
mounted() {
// this.handlerName()
this.fullName = "xxxx";
// this.fullName '李四'
},
methods: {
handlerName() {
this.obj.name = "xxxx";
},
},
};
常用指令
v-show
dispaly none
的切换
v-if
/v-else
v-html
v-text
v-for
(vue2 v-for
比v-if
优先级高,vu3v-if
优先级比v-for
高 )
v-cloak
[v-cloak] {dispaly:none}
v-once
静态内容
v-bind
=> :
v-on
=> @
<Child v-bind="$attrs" v-on="$listeners">Child>
v-model
<el-input v-model="keyword">el-input>
<el-input :value="keyword" @input="keyword = $event">el-input>
Vue.directive("指令名", {
// 生命周期
// 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
bind(el, binding, vnode, oldVnode) {
//
// binging.value 拿到指令值
// binding.modifiers 修饰符对象
},
// 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
inserted() {},
update() {},
componentUpdated() {},
// 只调用一次,指令与元素解绑时调用
unbind() {},
});
// 默认绑定 bind update 的生命周期
Vue.directive("指令名", function (el, binding, vnode, oldVnode) {});
.lazy、.number、.trim、.enter、.prevent、.self
.sync
<Dialog :visible.sync="visible">Child>
<Dialog :visible="visible" @update:visible="visible = $event">Child>
加了 scoped 就只作用于当前组件
<style scoped>style>
渲染规则
.a .b {} ==> .a .b[data-v-xx]{}
.a /deep/ .b {} ==> .a[data-v-xxx] .b {}
.a >>> .b {} ==> .a[data-v-xxx] .b {}
.a ::v-deep .b {} ==> .a[data-v-xxx] .b {}
vue-router
// 全局路由守卫
router.beforeEach((to, from, next) => {})
router.afterEach((to, from) => {})
new VueRouter({
mode: 'hash', // hash | history | abstract
// 滚动位置
scrollBehavior(to, from, savedPosition) {
if (savedPosition) return savedPosition
return { y: 0 }
},
routes: [
{
path: '/',
// 路由独享守卫
beforeEnter(to, from, next) {}
}
]
})
// 组件内的路由
beforeRouteEnter(to, from, next) {}
beforeRouteUpdate(to, from, next) {}
beforeRouteLeave(to, from, next) {}
// 跳转
this.$router.push({name: '', path: '', query: {}})
// 路由信息
this.$route.query this.$route.params
vuex
state getters mutations actions modules
// state
this.$store.state.userInfo;
// getters
this.$store.getters.userInfo;
// mutations
this.$store.commit("SET_USER_INFO", "传递数据");
// actions
this.$store.dispatch("logout").then((res) => {});
// -----------------------------------
// modules > user
// namespaced: true,
// state 拿 name
this.$store.state.user.avatar;
// getters
this.$store.getters.user.avatar;
// mutations
this.$store.commit("user/SET_TOKEN", "传递数据");
// actions
this.$store.dispatch("user/login").then((res) => {});
// -----------------------------------
// modules > user
// namespaced: false,
// state 拿 name
this.$store.state.user.avatar;
// getters
this.$store.getters.user.avatar;
// mutations
this.$store.commit("SET_TOKEN", "传递数据");
// actions
this.$store.dispatch("login").then((res) => {});
辅助函数
mapState, mapGetters, mapMutations, mapActions;
Vue3 的 8 种和 Vue2 的 12 种组件通信,值得收藏
聊一聊 Vue3 的 9 个知识点
Vue3 有哪些变化
Fragment
支持多个根节点、Suspense
可以在组件渲染之前的等待时间显示指定内容、Teleport
可以让子组件能够在视觉上跳出父组件(如父组件 overflow:hidden)v-memo
,可以缓存 html 模板,比如 v-for 列表不会变化的就缓存,简单说就是用内存换时间Tree-Shaking
,会在打包时去除一些无用代码,没有用到的模块,使得代码打包体积更小Composition API
可以更好的逻辑复用和代码组织,同一功能的代码不至于像以前一样太分散,虽然 Vue2 中可以用 minxin 来实现复用代码,但也存在问题,比如方法或属性名会冲突,代码来源也不清楚等Proxy
代替 Object.defineProperty
重构了响应式系统,可以监听到数组下标变化,及对象新增属性,因为监听的不是对象属性,而是对象本身,还可拦截 apply、has 等 13 种方法200%
里使用 v-bind
,给 CSS 绑定 JS 变量(color: v-bind(str)
)setup
代替了 beforeCreate 和 created 这两个生命周期onRenderTracked
会跟踪组件里所有变量和方法的变化、每次触发渲染时 onRenderTriggered
会返回发生变化的新旧值,可以让我们进行有针对性调试TS
写的,所以对 TS
的支持度更好IE11
选项式 API | Hook inside setup |
---|---|
beforeCreate |
Not needed* |
created |
Not needed* |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeUnmount |
onBeforeUnmount |
unmounted |
onUnmounted |
errorCaptured |
onErrorCaptured |
renderTracked |
onRenderTracked |
renderTriggered |
onRenderTriggered |
activated |
onActivated |
deactivated |
onDeactivated |
// main.js
import { createApp } from "vue";
import App from "./App.vue";
import HelloWorld from "./components/HelloWorld.vue";
const app = createApp(App);
// 全局组件
app.component("HelloWorld", HelloWorld);
// 全局属性
// vue2.0 Vue.prototype.$http
app.config.globalProperties.$http = () => {
console.log("http ==");
};
app.mount("#app");
<template>
<div>
<Child
title="父组件传递的title"
:modelValue="num"
@update:modelValue="num = $event"
@change="onChildChange"
v-model:visible="visible"
ref="childRef"
>Child>
div>
template>
<script>
import Child from "./Child-setup.vue";
import { reactive, ref } from "@vue/reactivity";
import { onMounted, provide } from "@vue/runtime-core";
export default {
components: { Child },
// data() {
// return {
// msg: '哈哈哈',
// }
// },
setup() {
const msg = ref("哈哈哈2"); // => reactive({value: 哈哈哈2 })
const obj = ref({ x: "xx" });
console.log(" obj.value: ", obj.value);
const user = reactive({ name: "张三", age: 18 });
const count = ref(0);
provide("count", count);
provide("http", () => {
console.log("$http >>>");
});
const add = () => {
count.value++;
};
const reduce = () => {
count.value--;
};
const num = ref(1);
const visible = ref(false);
// this.$refs.childRef x
// refs
// 1. 用字符串
const childRef = ref(null);
onMounted(() => {
console.log(" childRef.value: ", childRef.value);
});
let divRef;
const setDivRef = (el) => {
console.log(" el: ", el);
divRef = el;
};
return {
msg,
user,
count,
add,
reduce,
num,
visible,
childRef,
setDivRef,
};
},
methods: {
onChildChange() {},
},
};
script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
style>
<template>
<div>
<button @click="triggerEvent">触发事件button>
<div>num2:{{ num2 }}div>
<div>count:{{ count }}div>
modelValue:{{ modelValue }}
<button @click="add">+button>
<hr />
visible:{{ visible }}
<button @click="updateVisible">更新visiblebutton>
<teleport to="body">
<div v-if="visible">对话框div>
teleport>
div>
template>
<script>
import {
computed,
inject,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onMounted,
onUnmounted,
onUpdated,
watch,
watchEffect,
} from "@vue/runtime-core";
export default {
props: {
title: String,
modelValue: Number,
visible: Boolean,
},
// computed: {
// num2() {
// return this.modelValue * 2
// }
// },
emits: ["change", "update:modelValue", "update:visible"],
// 发生在 beforeCreate
// attrs 除了 class style,props 之外的属性
//
// watch: {
// title: {
// deep: true, // 深度简单
// }
// },
// 组合式API(composition), 选项式API(options)
setup(props, { emit, attrs, slots }) {
console.log(" attrs: ", attrs);
console.log(" props: ", props);
// computed
const num2 = computed(() => props.modelValue * 2);
// const num2 = computed({
// get: () => props.modelValue * 2,
// set: (val) => {
// ssss
// }
// })
//
const count = inject("count");
console.log(" count: ", count);
// watch
// this.$watch()
const unwatch = watch(
() => props.modelValue,
(newVal, oldValue) => {
console.log(" newVal: ", newVal);
if (newVal >= 10) {
// 取消监听
unwatch();
}
},
{
deep: true,
// immediate: true
}
);
// 自动收集依赖,所以会初始化的时候就执行一次
watchEffect(() => {
console.log(" props.modelValue: ", props.modelValue);
});
// hooks
onBeforeMount(() => {});
onMounted(() => {
console.log("哈哈哈");
});
onBeforeUpdate(() => {});
onUpdated(() => {});
onBeforeUnmount(() => {});
onUnmounted(() => {});
// keep-alive
onActivated(() => {});
onDeactivated(() => {});
// methods
const triggerEvent = () => {
emit("change", "传递的数据");
};
const add = () => {
emit("update:modelValue", props.modelValue + 1);
};
const updateVisible = () => {
console.log(" props.visible: ", props.visible);
emit("update:visible", !props.visible);
};
return {
triggerEvent,
add,
updateVisible,
num2,
count,
};
},
// beforeCreate() {
// console.log('beforeCreate')
// },
// created() {
// console.log('created')
// },
// beforeDestroy beforeUnmount
// destroyed unmounted
};
script>
<template>
<div>
<p>title: {{ title }}p>
<p>num2: {{ num2 }}p>
<p>count: {{ count }}p>
<div>
modelValue:{{ modelValue }}
<button @click="add">+button>
div>
<button @click="triggerEvent">触发事件button>
<input type="text" :value="inputValue" @input="onInputUpdate" />
<Foo>Foo>
div>
template>
<script setup>
import {
computed,
getCurrentInstance,
inject,
ref,
useAttrs,
useSlots,
} from "@vue/runtime-core";
import Foo from "./foo.vue";
// props
const props = defineProps({
title: String,
modelValue: Number,
});
// computed
const num2 = computed(() => props.modelValue * 2);
const count = inject("count");
// emit
const emit = defineEmits(["change", "update:modelValue", "update:visible"]);
const triggerEvent = () => {
emit("change", "传递的数据");
};
const add = () => {
emit("update:modelValue", props.modelValue + 1);
};
// 向父组件暴露自己的属性和方法
defineExpose({
num2,
test() {
console.log("888");
},
});
const attrs = useAttrs();
console.log(" attrs: ", attrs);
const solts = useSlots();
const ctx = getCurrentInstance();
const http = ctx.appContext.config.globalProperties.$http;
http("xxx");
const $http = inject("http");
$http();
// $ref: ref(false)
const inputValue = ref("");
const onInputUpdate = (event) => {
console.log(" event: ", event);
inputValue.value = event.target.value;
};
script>
https://shfshanyue.github.io/cheat-sheets/git
【万字】透过分析 webpack 面试题,构建 webpack5.x 知识体系
Entry
:入口,指示 Webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。
Output
:输出结果,告诉 Webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。
Module
:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
Chunk
:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
Loader
:模块代码转换器,让 webpack 能够去处理除了 JS、JSON 之外的其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。
Plugin
:扩展插件。在 webpack 运行的生命周期中会广播出许多事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 api 改变输出结果。常见的有:打包优化,资源管理,注入环境变量。
Mode
:模式,告知 webpack 使用相应模式的内置优化
hash
: 每次构建的生成唯一的一个 hash,且所有的文件 hash 串是一样的chunkhash
: 每个入口文件都是一个 chunk,每个 chunk 是由入口文件与其依赖所构成,异步加载的文件也被视为是一个 chunk, chunkhash是由每次编译模块,根据模块及其依赖模块构成 chunk 生成对应的 chunkhash, 这也就表明了每个 chunk 的 chunkhash 值都不一样, 也就是说每个 chunk 都是独立开来的,互不影响,每个 chunk 的更新不会影响其他 chunk 的编译构建
contenthash
:由文件内容决定,文件变化 contenthash 才会变化,一般配合 mini-css-extract-plugin
插件提取出 css
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HTMLWebpackPlugin = require("html-webpack-plugin");
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
use: [
{
// 把 style-loader替换掉,不要使用 style-loader了
loader: MiniCssExtractPlugin.loader,
options: {
outputPath: "css/",
},
},
"css-loader",
],
},
],
},
plugins: [
// ....
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash].css",
}),
],
};
一套骚操作下来,webpack 项目打包速度飞升 、体积骤减 ↓
玩转 webpack,使你的打包速度提升 90%
带你深度解锁 Webpack 系列(优化篇)
学习 Webpack5 之路(优化篇)- 近 7k 字
速度分析,可以使用 speed-measure-webpack-plugin
提升基础环境,nodejs 版本,webpack 版本
CDN
分包 html-webpack-externals-plugin
, externals
多进程、多实例构建 thread-loader
happypack(不再维护)
多进程并行构建打包uglifyjs-webpack-plugin
terser-webpack-plugin
缓存: webpack5 内置了cache
模块 、babel-loader
的 cacheDirectory
标志、cache-loader
, HardSourceWebpackPlugin
module.exports = {
// webpack5内置缓存
cache: {
type: 'filesystem', // 使用文件缓存
},
}
构建缩小范围 include
,exclude
加快文件查找速度resolve.alias
,resolve.extensions
, module.noParse
DllPlugin
babel
配置的优化
loader
babel-loader
将 es6
转换成 es5
, ts-loader
、vue-loader
eslint-loader
配置 enforce: 'pre'
这个loader最先执行css-loader
、style-loader
、postcss-loader
、less-loader
、sass-loader
file-loader
把文件转换成路径引入, url-loader
(比file-loader
多了小于多少的能转换成 base64)image-loader
svg-sprite-loader
处理 svgthread-loader
开启多进程 ,会在一个单独的 worker 池(worker pool)中运行cache-loader
缓存一些性能开销比较大的 loader 的处理结果plugin
html-webpack-plugin
将生成的css,js自动注入到html文件中,能对html文件压缩
copy-webpack-plugin
拷贝某个目录
clean-webpack-plugin
清空某个目录
webpack.HotModuleReplacementPlugin
热重载
webpack.DefinePlugin
定义全局变量
mini-css-extract-plugin
提取 CSS 到独立 bundle 文件。
optimize-css-assets-webpack-plugin
压缩css
purgecss-webpack-plugin
会单独提取 CSS 并清除用不到的 CSS
uglifyjs-webpack-plugin
❌(不推荐) 压缩js、多进程 parallel: true
terser-webpack-plugin
压缩js, 可开启多进程压缩、推荐使用
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true, // 多进程压缩
}),
],
},
};
Happypack
❌(不再维护) 可开启多进程
HardSourceWebpackPlugin
缓存
speed-measure-webpack-plugin
打包构建速度分析、查看编译速度
webpack-bundle-analyzer
打包体积分析
compression-webpack-plugin
gzip 压缩
前端性能优化 24 条建议(2020)
MiniCssExtractPlugin
),js(UglifyPlugin
),html(html-webpack-plugin
)文件压缩,清除无用的代码,tree-shaking
(需要 es6 的 import 才支持),gzip 压缩(compression-webpack-plugin
)hash
,contenthash
不容错过的 Babel7 知识
核心库 @babel/core
Polyfill
垫片
CLI 命令行工具 @babel/cli
插件
预设:包含了很多插件的一个组合,@babel/preset-env
@babel/preset-react
@babel/preset-typescript
polyfill
Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如Iterator
、Generator
、Set
、Map
、Proxy
、Reflect
、Symbol
、Promise
等全局对象,以及一些定义在全局对象上的方法(比如Object.assign
)都不会转码。
举例来说,ES6 在Array
对象上新增了Array.from
方法。Babel 就不会转码这个方法。如果想让这个方法运行,可以使用core-js
和regenerator-runtime
(后者提供 generator 函数的转码),为当前环境提供一个垫片。
@babel/plugin-transform-runtime
Babel
会使用很小的辅助函数来实现类似 _createClass
等公共方法。默认情况下,它将被添加(inject
)到需要它的每个文件中。
如果你有 10 个文件中都使用了这个 class
,是不是意味着 _classCallCheck
、_defineProperties
、_createClass
这些方法被 inject
了 10 次。这显然会导致包体积增大,最关键的是,我们并不需要它 inject
多次。
@babel/plugin-transform-runtime
是一个可以重复使用 Babel
注入的帮助程序,以节省代码大小的插件。
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
//.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage", // 配置 polyfill 动态导入
"corejs": 3 // core-js@3
}
]
],
"plugins": [
[
"@babel/plugin-transform-runtime"
]
]
}
如果命中强缓存–就不用像服务器去请求
Expires
设置时间,过期时间 expires: Tue, 15 Oct 2019 13:30:54 GMT
通过本地时间和 expires 比较是否过期,如果过期了就去服务器请求,没有过期的话就直接使用本地的
缺点:本地时间可能会更改, 导致缓存出错
Cache-Control
HTTP1.1 中新增的
max-age 最大缓存多少毫秒,列如 Cache-Control: max-age=2592000
no-store (每次都要请求,就连协商缓存都不走)表示不进行缓存,缓存中不得存储任何关于客户端请求和服务端响应的内容。每次 由客户端发起的请求都会下载完整的响应内容。Cache-Control: no-store
no-cache(默认值)表示不缓存过期的资源,缓存会向源服务器进行有效期确认后处理资源,也许称 为 do-notserve-from-cache-without-revalidation 更合适。浏览器默认开启的是 no-cache,其 实这里也可理解为开启协商缓存
public 和 private
public 与 private 是针对资源是否能够被代理服务缓存而存在的一组对立概念
当我们为资源设置了 pubile,那么它既可以被浏览器缓存也可被代理服务器缓存。设置为
private 的时候,则该资源只能被浏览器缓存,其中默认值是 private。
max-age 和 s-maxage
s-maxage 只适用于供多用户使用的公共服务器上(如 CND cache),并只对 public 缓存有效
需要向服务器请求,如果没有过期,服务器会返回 304,
服务器响应 ETag 值,浏览器携带的是 If-None-Match(携带的是上一次响应的 ETag),服务拿到这 If-None-Match 值后判断过期–> 没有过期 304,并且返回 ETag
二者的值都是服务器为每份资源分配的唯一标识字符串。
• 浏览器请求资源,服务器会在响应报文头中加入 ETag 字段。资源更新的时候,服务端的
ETag 值也随之更新
• 浏览器再次请求资源,会在请求报文头中添加 If-None-Match 字段,它的值就是上次响应
报文中的 ETag 值,服务器会对比 ETag 和 If-None-Match 的值是否一致。如果不一致,服务
器则接受请求,返回更新后的资源,状态码返回 200;如果一致,表明资源未更新,则返回
状态码 304,可继续使用本地缓存,值得注意的是此时响应头会加上 ETag 字段,即使它没
有变化
Last-Modified 和 If-Modified-Since 时间戳
缺点: 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说 1s 内修改了 N 次),
If-Modified-Since 可查到的是秒级,这种修改无法判断
四部曲
AO
对象AO
的属性名,值为undefined
// 预编译
function foo(test/* 形参 */) {
console.log(' test: ', test) // function(){}
var test = 2
var str = 'bs'
console.log(' test: ', test) // 2
// 函数声明
function test() {}
// 函数表达式
str = function() {}
console.log(' test: ', test) // 2
}
// 预编译 四部曲
// 1. 创建AO对象
// 2. 找形参和变量声明,将变量和形参名作为AO的属性名,值为undefined
// 3. 将实参值和形参值相统一
// 4. 在函数体里面找到函数声明,值赋予函数体
// AO {
// test: undefined
// str: undefined
// }
// AO {
// test: 1
// str: undefined
// }
// AO {
// test: 1
// str: function() {}
// }
foo(1/*实参*/)
function foo (a, b, c) {
console.log(a)
console.log(b)
var a = '222'
function a() {}
var b = function() {}
console.log(a)
console.log(b)
console.log(' a, b, c: ', a, b, c)
}
// AO {
// a : '222',
// b : function() {},
// c : 3
// }
foo(1, 2, 3)
var a = 22
// let a = 22
// window.a ==> 22
全局
undefined
一次弄懂Event Loop(彻底解决此类面试问题)
JS
是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,Promise.then
,MutationObserver
,宏任务的话就是setImmediate setTimeout setInterval
MacroTask(宏任务)*
script
全部代码、setTimeout
、setInterval
、setImmediate
(浏览器暂时不支持,只有 IE10 支持,具体可见MDN
)、I/O
、UI Rendering
。MicroTask(微任务)
Process.nextTick(Node独有)
、Promise.then
、Object.observe(废弃)
、MutationObserver
执行完一个宏任务,会执行所有的微任务
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 0);
new Promise((resolve) => {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log("script end");
执行结果
script start
promise1
script end
promise2
setTimeout
在 11 之前的版本,会在每个阶段之后执行所有的微任务
在 11 版本及之后,会每执行完一个宏任务,就会清空所用的微任务(和浏览器保存一致)
new Promise((resolve) => {
console.log("new Promise 1");
resolve();
}).then(() => {
console.log("new Promise then");
});
setTimeout(() => {
console.log("timer1");
new Promise((resolve) => {
console.log("timer1 new Promise");
resolve();
}).then(() => {
console.log("timer1 new Promise then");
});
Promise.resolve().then(() => {
console.log("timer1 Promise then");
});
});
setTimeout(() => {
console.log("timer2");
Promise.resolve().then(() => {
console.log("timer2 Promise then");
});
});
console.log("start end");
在 node11 版本之前(不包含 11)
new Promise 1
start end
new Promise then
timer1
timer1 new Promise
timer2
timer1 new Promise then
timer1 Promise then
timer2 Promise then
在 node11 版本及之后
new Promise 1
start end
new Promise then
timer1
timer1 new Promise
timer1 new Promise then
timer1 Promise then
timer2
timer2 Promise then
Node
的Event loop
一共分为6个阶段,每个细节具体如下:
timers
: 执行setTimeout
和setInterval
中到期的callback
。pending callback
: 上一轮循环中少数的callback
会放在这一阶段执行。idle, prepare
: 仅在内部使用。poll
: 最重要的阶段,执行pending callback
,在适当的情况下回阻塞在这个阶段。check
: 执行setImmediate
(setImmediate()
是将事件插入到事件队列尾部,主线程和事件队列的函数执行完成之后立即执行setImmediate
指定的回调函数)的callback
。close callbacks
: 执行close
事件的callback
,例如socket.on('close'[,fn])
或者http.server.on('close, fn)
。面试官,不要再问我三次握手和四次挥手
为什么需要三次握手,两次不可以吗
为了防止失效的连接请求又传送到主机,因而产生错误。
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请
求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于 TCP 的客
户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条
报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。
此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失
效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不
必要的错误和资源的浪费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了
那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器
收不到确认,就知道客户端并没有请求连接。
挥手为什么需要四次?
因为当服务端收到客户端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。其中ACK 报文是用来应答的,SYN 报文是用来同步的。但是关闭连接时,当服务端收到 FIN 报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉客户端,“你发的 FIN 报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。故需要四次挥手。
2MSL
等待状态
TIME_WAIT 状态也成为2MSL
等待状态。每个具体 TCP 实现必须选择一个报文段最大生存时间 MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为 TCP 报文段以 IP 数据报在网络内传输,而 IP 数据报则有限制其生存时间的 TTL 字段。
对一个具体实现所给定的 MSL 值,处理的原则是:当 TCP 执行一个主动关闭,并发回最后一个 ACK,该连接必须在 TIME_WAIT 状态停留的时间为 2 倍的 MSL。这样可让 TCP 再次发送最后的 ACK 以防这个 ACK 丢失(另一端超时并重发最后的 FIN)。
这种 2MSL 等待的另一个结果是这个 TCP 连接在 2MSL 等待期间,定义这个连接的插口(客户的 IP 地址和端口号,服务器的 IP 地址和端口号)不能再被使用。这个连接只能在 2MSL 结束后才能再被使用。
http: 最广泛网络协议,BS 模型,浏览器高效。
https: 安全版,通过 SSL 加密,加密传输,身份认证,密钥
1 https 相对于 http 加入了 ssl 层, 加密传输, 身份认证;
2 需要到 ca 申请收费的证书;
3 安全但是耗时多,缓存不是很好;
4 注意兼容 http 和 https;
5 连接方式不同, 端口号也不同, http 是 80, https 是 443
明文: 普通的文本
密钥:把明文加密的那个钥匙
密文: 把明文加密
明文+密钥==>密文==>密钥==解密=>明文
对称加密 解密的 key(密钥)和解密的 key 是同一个 3 + 1
非对称加密 私钥和公钥
10个常见的前端手写功能,你全都会吗
// event bus
class EventBus {
constructor() {
this.events = {}
}
on(name, callback) {
const { events } = this
if (!events[name]) {
events[name] = []
}
events[name].push(callback)
}
emit(name, ...args) {
const handlers = this.events[name]
handlers && handlers.forEach(fn => {
fn.apply(this, args)
})
}
off(name, callback) {
const { events } = this
if (!events[name]) return
events[name] = events[name].filter(fn => fn !== callback)
}
once(name, callback) {
const handler = function() {
callback.apply(this, arguments)
this.off(name, handler)
}
this.on(name, handler)
}
clear() {
this.events = {}
}
}
// 数据偏平化
function flatter(arr) {
return arr.reduce((prev, curr) => {
return Array.isArray(curr) ?
[...prev, ...flatter(curr)]
: [...prev, curr]
}, [])
}
// 手写 new
function myNew(ctr, ...args) {
const obj = Object.create(ctr.prototype)
myNew.target = ctr
const result = ctr.apply(obj, args)
if (result && (typeof result === 'function' || typeof result === 'function')) {
return result
}
return obj
}
Function.prototype.myCall = function(context, ...args) {
context = context || window
const fn = Symbol()
context[fn] = this
const result = context[fn](...args)
delete context[fn]
return result
}
//bind实现要复杂一点 因为他考虑的情况比较多 还要涉及到参数合并(类似函数柯里化)
Function.prototype.myBind = function (context, ...args) {
if (!context || context === null) {
context = window;
}
// 创造唯一的key值 作为我们构造的context内部方法名
let fn = Symbol();
context[fn] = this;
let _this = this;
// bind情况要复杂一点
const result = function (...innerArgs) {
// 第一种情况 :若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符使用,则不绑定传入的 this,而是将 this 指向实例化出来的对象
// 此时由于new操作符作用 this指向result实例对象 而result又继承自传入的_this 根据原型链知识可得出以下结论
// this.__proto__ === result.prototype //this instanceof result =>true
// this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
if (this instanceof _this === true) {
// 此时this指向指向result的实例 这时候不需要改变this指向
this[fn] = _this;
this[fn](...[...args, ...innerArgs]); //这里使用es6的方法让bind支持参数合并
} else {
// 如果只是作为普通函数调用 那就很简单了 直接改变this指向为传入的context
context[fn](...[...args, ...innerArgs]);
}
};
// 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
// 实现继承的方式: 使用Object.create
result.prototype = Object.create(this.prototype);
return result;
};
function limitRequest(requests, limit = 3) {
requests = requests.slice()
return new Promise((resolve, reject) => {
let count = 0
const len = requests.length
while (limit > 0) {
start()
limit--
}
function start() {
const promiseFn = requests.shift()
promiseFn?.().finally(() => {
count++ // 一定要通过 count 判断、不能通过 requests.length 判断是否为空,这样不对的
if (count === len) {
// 最后一个
resolve()
} else {
start()
}
})
}
})
}
// 测试
const arr = []
for (let value of '12345') {
arr.push(() => fetch(`https://www.baidu.com/s?ie=UTF-8&wd=${value}`))
}
limitRequest(arr)