已同步到掘金、CSDN
掘金地址: https://juejin.cn/post/7075332630417244173
CSDN 地址:https://blog.csdn.net/z1832729975/article/details/123431083
个人整理了很多网上常见的面试题,希望也能通过这来复习
内容有点多,可能 CSDN 上预览效果不好,想要 markdown 文档的可以私信我,推荐使用Typora
看
比较好的面试题
2021 年我的前端面试准备
2021 年前端面试必读文章【超三百篇文章/赠复习导图】
IE: trident 内核
Firefox:gecko 内核
Safari: webkit 内核
Opera: 以前是 presto 内核,Opera 现已改用 GoogleChrome 的 Blink 内核
Chrome: Blink(基于 webkit,Google 与 Opera Software 共同开发)
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
,他们都是文字类块级标签,里面不能放其他块级元素。
使用目的
告诉浏览器,用户在移动端时如何缩放页面
with=device-width
将布局视窗(layout viewport)的宽度设置为设备屏幕分辨率的宽度
initial-scale=1
页面初始缩放比例为屏幕分辨率的宽度
maximum-scale=1
指定用户能够放大的最大比例
minimum-scale=1
指定用户能够缩小的最大比例
label 标签来定义表单控制间的关系,当用户选择该标签时,浏览器会自动将焦点转到和标签相关的表单控件上。
canvas 标签的 width 和 height 是画布实际宽度和高度,绘制的图形都是在这个上面。
而 style 的 width 和 height 是 canvas 在浏览器中被渲染的高度和宽度。
如果 canvas 的 width 和 height 没指定或值不正确,就被设置成默认值 。
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 自适应
iframe 是一种框架,也是一种很常见的网页嵌入方
iframe 的优点:
iframe 的缺点:
link 是 XHTML 标签,除了加载 CSS 外,还可以定义 RSS 等其他事务;
@import 属于 CSS 范畴, 只能加载 CSS。
link 引用 CSS 时,在页面载入时同时加载;@import 需要页面网页完全载入以后加载。link
无兼容问题;@import 是在 CSS2.1 提出的,低版本的浏览器不支持。
link 支持使用 Javascript 控制 DOM 去改变样式;而@import 不支持。
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 父元素上添加。
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事
件触发机制
多数显示器默认频率是 60Hz,即 1 秒刷新 60 次,所以理论上最小间隔为
1/60*1000ms = 16.7ms
单冒号(:)用于 CSS3 伪类,双冒号(::)用于 CSS3 伪元素。 ::before 就是以一个子元素的存在,定义在元素主体内容之前的一个伪元素。并不存在于 dom 之中,只存在在页面之中。 :before 和 :after 这两个伪元素,是在 CSS2.1 里新出现的。起初,伪元素的前缀使用的是单 冒号语法,但随着 Web 的进化,在 CSS3 的规范里,伪元素的语法被修改成使用双冒号,成 为::before ::after
CSS Sprites 其实就是把网页中一些背景图片整合到一张图片文件中,再利用 CSS 的 “background-image”,“ background-repeat ”,“ background-position”
的 组 合 进 行 背 景 定 位 , background-position 可以用数字能精确的定位出背景图片的位置。这样可以减少很多图片请 求的开销,因为请求耗时比较长;请求虽然可以并发,但是也有限制,一般浏览器都是 6 个
重绘(repaint 或 redraw):当盒子的位置、大小以及其他属性,例如颜色、字 体大小等都确定下来之后,浏览器便把这些原色都按照各自的特性绘制一遍,将 内容呈现在页面上。重绘是指一个元素外观的改变所触发的浏览器行为,浏览器 会根据元素的新属性重新绘制,使元素呈现新的外观。
触发重绘的条件:改变元素外观属性。如:color,background-color 等。 注意:table 及其内部元素可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多花两倍时间,这就是我们尽量避免使用 table 布局页面的 原因之一。
重排(重构/回流/reflow):当渲染树中的一部分(或全部)因为元素的规模尺寸, 布局,隐藏等改变而需要重新构建, 这就称为回流(reflow)。每个页面至少需要 一次回流,就是在页面第一次加载的时候。
重绘和重排的关系:在回流的时候,浏览器会使渲染树中受到影响的部分失效, 并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕 中,该过程称为重绘。所以,重排必定会引发重绘,但重绘不一定会引发重排。
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
属性是否在某个实例对象的原型链上, 不能判断基本数据类型
// 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
// 深拷贝
// 1. 最简单的,JSON.stringify,但这个有问题,看下面有说明
let arr2 = JSON.parse(JSON.stringify(arr));
// 2. 自己封装,要递归处理
实现深拷贝-简单版
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;
}
JSON.stringify
问题如果有循环引用就报错
Symbol
、function
、undefined
会丢失
布尔值
、数字
、字符串
的包装对象会转换成原始值
NaN
、Infinity
变成 null
Date
类型的日期会变成字符串
RegExp
、Error
被转换成了空对象 {}
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 模块输出的是值的引用。
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 fn = Symbol();
context[fn] = this;
var res = context[fn](...arguments[1]);
delete context[fn];
return res;
};
实现一个 bind
// 最终版 删除注释 详细注释版请看上文
Function.prototype.bind =
Function.prototype.bind ||
function bind(thisArg) {
if (typeof this !== "function") {
throw new TypeError(this + " must be a function");
}
var self = this;
var args = [].slice.call(arguments, 1);
var bound = function () {
var boundArgs = [].slice.call(arguments);
var finalArgs = args.concat(boundArgs);
if (this instanceof bound) {
if (self.prototype) {
function Empty() {}
Empty.prototype = self.prototype;
bound.prototype = new Empty();
}
var result = self.apply(this, finalArgs);
var isObject = typeof result === "object" && result !== null;
var isFunction = typeof result === "function";
if (isObject || isFunction) {
return result;
}
return this;
} else {
return self.apply(thisArg, finalArgs);
}
};
return bound;
};
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’ 内部调用 .join 方法
{}.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
和原型promise、generator、async/await
mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,
冒泡的过程。对应的移除事件是 mouseout
mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是
不会冒泡,对应的移除事件是 mouseleave
与 setTimeout 和 setInterval 不同,requestAnimationFrame 不需要设置时间 间隔, 大多数电脑显示器的刷新频率是 60Hz,大概相当于每秒钟重绘 60 次。大多数浏 览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频 率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是 1000ms/60,约 等于 16.6ms。 RAF 采用的是系统时间间隔,不会因为前面的任务,不会影响 RAF,但是如果前 面的任务多的话,会响应 setTimeout 和 setInterval 真正运行时的时间间隔。 特点:
(1)requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次 重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。
(2)在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回 流,这当然就意味着更少的 CPU、GPU 和内存使用量
(3)requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览 器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停, 有效节省了 CPU 开销。
vue2 是通过Object.defineProperty
来实现响应式的,所以就会有一些缺陷
arr[index]='xxx'
)vue 不能做响应式处理可用下面的解决响应式
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
因为 Vue 的组件可能会在很多地方使用, 会产生多个实例,如果返回的是对象的, 这些组件之间的数据是同一份(引用关系),那么修改其中一个组件的数据,另外一个组件的数据都会被修改到
看这个视频,你能给面试官说这些,你就很不错了,vue 和 react 的差不多 https://www.bilibili.com/video/BV1wy4y1D7JT?p=48
…待更新
下面是照抄的一段话,个人觉得这个主要看个人理解,如果看过源码理解 MVVM 这方面的,就很简单
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持
各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体步骤:
第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter
和 getter
这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化
第二步:compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,
并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通
知,更新视图
第三步:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个 update()方法
3、待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的
回调,则功成身退。
第四步:MVVM 作为数据绑定的入口,整合 Observer、
Compile 和 Watcher 三者,通过 Observer
来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起
Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数
据 model 变更的双向绑定效果。
所以也可以根据这个来说明为什么 给Vue
对象不存在的属性设置值的时候不生效,直接修改数组的index
不生效
Vue 提供了 Vue.set(对象|数组, key|index, 值)
修改触发响应式,重新数组的原型方法实现响应式
vue extend 和 mixins 的区别, mixins 里面的 函数和本身的函数重名了使用哪一个,mixins 里面的生命周期和本身的生命周期哪一个先执行
…待更新
// component 动态组件,通过is设置要显示的组件
就是给组件设置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
=> @
v-model
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
加了 scoped 就只作用于当前组件
渲染规则
.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");
num2:{{ num2 }}
count:{{ count }}
modelValue:{{ modelValue }}
visible:{{ visible }}
对话框
title: {{ title }}
num2: {{ num2 }}
count: {{ count }}
modelValue:{{ modelValue }}
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 文件。 extract-text-webpack-plugin
optimize-css-assets-webpack-plugin
压缩 css webpack5 推荐css-minimizer-webpack-plugin
purgecss-webpack-plugin
会单独提取 CSS 并清除用不到的 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"
]
]
}
参考:https://blog.csdn.net/weixin_43745075/article/details/115482227
同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制内容有:
但是有三个标签是允许跨域加载资源: