9、事件循环机制(Event loop)宏任务(macro-task)微任务(micro-task)
2、 将'a.b.c.d'转换成{a:{b:{c:{d:{}}}}},console.log(JSON.stringify(transform('a.b.c.d')))
\
前端面试笔记****
前端基础相关****
1、常用设计模式:****
【工厂模式】不暴露对象的具体逻辑,将逻辑封装,调用者创建对象只需要名称和传入参数。
【单例模式】无论创建多少次,对象都指向同一个。应用:全局缓存、浏览器全局对象。
【装饰器模式】不改变原对象的基础上,通过拓展功能和属性来实现更复杂的逻辑
【链模式】链式调用的主要方法,在自身方法中返回自身
【观察者模式】一般用来解决一对多的关系,一被观察者和多观察者。
一个被观察者,内部需要维护一个与所有观察它的对象数组
多个观察者,内部必须的一个 update 函数,需要在被观察者状态更新后,作出响应。
2、图片懒加载、滚动加载、检测曝光度等功能实现原理****
(1)原生Intersection Observer API
(2)老方法,监听 scroll 事件以及通过 Element.getBoundingClientRect() 获取节点位置的方式
Intersection Observer API:****
构造函数 IntersectionObserver 接受两个参数
callback:发生变化的回调函数
-entries****
--boundingClientRect 目标元素getBoundingClientRect()返回值
--interpRatio 一个对象,包含目标元素与根元素交叉区域getBoundingClientRect() 的返回值。
--isIntersecting 布尔值,如果目标元素与根元素相交,返回true。isIntersecting 为true,则元素至少达到了 thresholds属性中的某一个值
--isVisible 是否可见,需要options参数传入trackVisibility:true,delay 大于100,否则不生效,永远为false。受opacity:0等样式影响
--rootBounds 根元素getBoundingClientRect()返回值
--target 被观察目标元素,DOM节点
--time 首次创建观察者到触发此交集改变的时间,
-observer
options:配置对象
-root 判断元素是否可见的区域,目标元素的根元素,不指定,默认是document
-rootMargin 扩大和缩小判定范围 默认值0px 0px 0px 0px,指定了root,可用百分比。在容器原有范围内,减去Margin这部分
-threshold 0-1之间,指示触发前应该可见的百分比,可以是数组,以创建多个触发点,也被称为阙值。默认为0,即一旦可见就执行,1则是完全可见执行
为数组时,[0,0.25,0.75]每个状态都执行一次
-trackVisibility 布尔值,指示当前观察器是否将跟踪目标可见性的更改,默认false
-delay 数字,回调函数执行的延迟时间(毫秒)
let options = {
//观察区域,父级'#scrollArea
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);
let target = document.querySelector('#listItem');
//绑定被观察元素'#listItem
observer.observe(target);
3、APM性能监控****
前端性能指标:
白屏时间、首屏时间、用户可交互时间、总下载时间、http请求时间
web性能监控方式:合成监控(模拟测试,不影响页面本身)、真实用户监控(友有侵入性,对页面性能有影响)
我一般用performance来看
4、HTML语义化****
用合适的标签做合适的事,比如h1,p,ul,header,footer,nav
便于开发和维护,可读性高
没有css时,也有良好的页面结构,便于阅读
SEO友好,结构清晰,便于解析
5、web性能优化****
回流一定会触发重绘****
只说前端部分,运维和后端的ngnix,接口做缓存之类的就不说了****
1、减少HTTP请求数量
http请求,资源加载,图片懒加载(常用打包工具都会将图片base64化,js、html文件进行压缩)
2、将外部脚本置底
6、闭包****
外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象,这就是闭包的重要概念。
闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。
在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。
造成内存泄漏,变量污染
7、JS执行上下文****
当 JavaScript 代码在运行的时候, 它所在的执行环境通常认为是以下其中之一:
Global code – 默认环境,你的代码首次执行的地方。
Function code – 当代码执行进入到函数体当中。
Eval code – 在 eval 函数内部执行的文本。
有且只能有1个全局上下文, 并且可以被程序中其他的上下文访问到。
可以有很多个函数上下文, 每个函数调用都创造一个新的上下文, 并创建出一个局部作用域,任何在作用域内部声明的东西都不能被当前函数作用域外部访问到(闭包)
代码的执行流进入内部函数,这将创建一个新的执行上下文,它被压入现有栈的顶部。浏览器永远会执行当前栈中顶部的执行上下文 一旦函数在当前执行上下文执行完毕,它会被从栈的顶部弹出,然后将控制权移交给当前栈的下一个上下文当中****
执行上下文组成:
变量对象Variable Object(变量声明、函数声明、函数形参)
作用域链 Scope Chain
this指针
上下文执行步骤
步骤 1-1:根据形参创建arguments,用实参赋值给对应的形参,没有实参的赋值为undefined
// 已声明的变量,直接跳过,不会影响其内容
步骤 1-2:扫描函数声明
步骤 1-3:扫描变量声明
步骤 1-4:按顺序执行代码语句
多个上下文执行****
不是异步函数,单线程返回结果3 2 1****
function f1(){
f2();
console.log(1)
}
function f2(){
f3();
console.log(2)
}
function f3(){
console.log(3)
}
f1(); // 3 2 1
全局执行上下文只有一个,并且在栈底。
当浏览器关闭时,全局执行上下文才会出栈
函数执行上下文可以有多个,并且函数每调用执行一次(即使是调用自身),就会生成一个新的函数执行上下文。
JS是单线程,所以是同步执行,执行上下文栈中,永远是处于栈顶的是执行状态****
VO或是AO只有一个,创建过程的顺序是:参数声明>函数声明>变量声明****
每个EC可以抽象为一个对象,这个对象包含三个属性:作用域链、VO/AO、this****
8、块级元素和行内元素各自的特性:****
1.块级元素 body h1-h6 html table button hr p ol ul dl cnter div****
1)总是从新的一行开始,即各个块级元素独占一行,默认垂直向下排列;
2)高度、宽度、margin及padding都是可控的,设置有效,有边距效果;
3)宽度没有设置时,默认为100%;
4)块级元素中可以包含块级元素和行内元素。
2.行内元素 title lable span br a style em b i strong
1)和其他元素都在一行,即行内元素和其他行内元素都会在一条水平线上排列;
2)高度、宽度是不可控的,设置无效,由内容决定。
9、事件循环机制(Event loop)宏任务(macro-task)微任务(micro-task)****
执行上下文中,异步事件会被放到事件队列里****
事件循环:就是循环事件队列里每一个任务队列,一个事件循环中可以有一个或者多个任务队列,在事件循环中,每进行一次循环的关键步骤主要有:
(1)在此次循环中选择最先进入队列的任务,如果有则执行一次
(2)检查是否存在微任务(new Promise(()=>{ }).then(res=>{})),如果存在则不停地执行,直至清空微任务队列,再执行同步的宏任务
(3)更新render(DOM渲染)
10、跨域问题****
因为浏览器的同源策略限制,会产生跨域问题
同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)****
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
跨域实现方法:
JSONP:(不建议) 类似于把xhr请求包装成script资源来进行请求,前端dataType 改为 "jsonp" ,后端也要相应做出协议修改,不建议使用,很多时候不符合使用场景
CORS:服务端设置header('Access-Control-Allow-Origin: ');***
Nginx配置服务端入口配置(常用): 根据服务端接口地址特征配置对应跨域地址
CORS跨域请求
11、 CORS 简单请求和非简单请求****
浏览器将请求分为两大类:简单请求(simple request)和非简单请求(not-so-simple request)****
简单请求
请求方法是以下三种方法:HEAD、GET、POST
HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
非简单请求(凡是不是简单请求的,就是非简单请求)****
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或者DELETE,或者content-type是application/json。
非简单请求会在正式通信之前,增加一次HTTP的查询请求options,称为“预检”请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
12、Web安全 XXS****
在web页面中注入script脚本进行攻击
反射型XSS 诱惑用户打开有恶意代码的链接,链接里的代码会直接在浏览器里执行,通过浏览器拿到cookie等信息
存储型XSS 将代码恶意发送到服务器
后端需要对提交的数据进行过滤。
前端也可以做一下处理方式,比如对script标签,将特殊字符替换成HTML编码这些等。
DOM XSS 注入恶意代码,修改页面信息
SQL注入 恶意注入SQL命令
cookie安全策略: cookie采用httpOnly
少用v-html,确保内容安全才能用
来自不信任的信息源的数据要进行编码和过滤
内容安全策略 (CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段
设置http header => Content-Security-Policy:
设置meta标签的方式
13、ES6、7、8...****
ES6****
1)let、const****
let:块级作用域,不允许变量提升
const:声明常量,设定后值不可改变(引用类型除外)
2)解构赋值:let [a,b,c]=[1,2,3]
3)箭头函数: ()=>{ },不会改变this指向
4)...操作符:
var a = ['sam','lily','terry'];
sayHello(p1,p2,p3){};sayHello(...a)
5)Set、Map(iterable类型)****
for...in会遍历出数组里除length外的属性名称,所以有了for...of修复了这个问题。for...of针对iterable类型的数据遍历
-Set:类数组,没有索引,内部元素是唯一的,可以用来去重(Array.form(set)转为数组)
实例方法:****
****size()获取集合中个数、add(val)添加、delete(val)删除、
has(val)是否包含、forEach(val,index)遍历方法
-Map:类对象,普通对象的key必须是字符串或数字,Map的key可以是任何数 据类型
实例方法:
size()同上、set(key,val)设置map中键值对、delete(key)删除、has(key)
forEach(val,index)遍历方法
6)类class: 引入class的概念,更接近传统写法
//定义类
名为Point的类,初始化了两个属性x和y
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
let a = new Point("哈哈","嘿嘿"),创建对象会自动调用构造函数方法constructor()
7)函数参数默认值: function test(name="test1",type=1){}
8)拼接字符串:我的名字是${name},我的年龄是${age}****
9)Promise****
三种状态pending进行中、fulfilled已成功、rejected已失败
var promise = new Promise(function(resolve,reject){
if(异步成功){
resolve(...)
}else{
reject(...)
}
})
resolve(...)作用是把promise状态由‘未完成’变为‘成功’
pending》fulfilled
reject(...)作用是把promise状态由‘未完成’变为‘失败’
pending》rejected
实例生成后,用then接受两个回调参数,resolve()和reject()promise.then((...)=>{},(...)=>{})
或者promise.then((...)=>{}).catch((...)=>{})
Promise.all([p1,p2,p3])****
// 生成一个Promise对象的数组****
var promises = [1,2,3,4,5,6].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
})
上面代码中,promises 是包含6个 Promise 实例的数组,只有这6个实例的状态都变成 fulfilled,或者其中有一个变为 rejected, 才会调用 Promise.all 方法后面的回调函数
Promise.race()****
只要有一个 promise 对象进入 FulFilled 或者 Rejected 状态的话,Promise.rece 就会继续进行后面的处理
10)Generator 函数****
通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案
区别:function 后面,函数名之前有个 、函数内部有 yield 表达式***
function* func(){
console.log("one");
yield '1';
console.log("two:" + x);
yield '2';
console.log("three:" + (x + y));
return '3';
}
用.next()方法调用,next 传入参数的时候,会作为上一步yield的返回值
var f = func();
f.next()//one f.next(10)//one
f.next()//two:undefined f.next(20)//two:20
f.next()//three:undefined f.next(30)//three:50
应用场景:for... of
ES7****
1)includes****
检查是否存在一个值
includes(val,start)val需查找的字符串 start从哪个位置开始查找,默认0
console.log(‘React mode’.toLowerCase().includes('react'))// === true)
console.log([1, 2, 3].includes(4)) // === false)
console.log([1, 2, NaN].includes(NaN)) // === true)
console.log(['a', 'b', 'c'].includes('a', 1)) // === false)
ES8****
1)Object.values/Object.entries方法****
let obj = {a:1,b:2,c:3}****
Object.keys(obj).forEach((key)=>{
//key='a', 'b', 'c'
})
Object.values(obj).forEach((val)=>{
//val=1, 2, 3
})
Object.entries(obj).forEach(([key, value])=>{
//key=['a', 1], ['b', 2], ['c', 3]
})
2)Async/Await****
promise写异步代码要环环相套,比较麻烦
Async/Await使异步代码看上去更像同步代码,便于阅读
3)string.padStart、string.padEnd
"lbj".padStart(8,["我的名字是"])//我的名字是lbj
"lbj".padEnd(8,["我的名字是"])//lbj我的名字是
ES9****
Promise.finally()
不管Promise状态如何,都会在最后执行一次****
ES10****
Array.prototype.flat(Infinity)
flat()按照指定深度递归遍历数组,并返回合并数组,可以除掉数组中空数据
Infinity为深度参数,默认递归一级
14、JSbridge****
JSBridge是一种桥接器,通过JS引擎或Webview容器为媒介 ,约定协议进行通信,实现Native端和Web端双向通信的一种机制。
注入API方式:Native端通过 WebView 提供的接口,向 JavaScript 的 Context(window)中注入对象。在Web中通过注入的对象调用Native方法
拦截URL Schema: jsbridge://showToast?text=
15、js数据类型,判断方法****
原始数据类型:number、string、boolean、undefined、null、symbol****
typeof判断,null的类型判断,不能使用typeof、instanceof、constructor
引用类型的object和function也能用typeof判断
引用数据类型:object、function、Array、Date、RegExp****
instanceof: 返回布尔值
instanceof 用于判断一个变量是否属于某个对象的实例
var a = new Array();
alert(a instanceof Array); // true
alert(a instanceof Object) // true
这是因为 Array 是 object 的子类
var a = new test();
alert(a instanceof test) // true
constructor属性****
var a = new Array();****
let date = new Date();
console.log(typeof date); // object
console.log(date.constructor); // Date
let reg = new RegExp("/^/d*$/") ;
console.log(typeof reg); // object
console.log(reg.constructor); // RegExp
Object.prototype.toString()****
call()是用来改变this指向的,同理可以使用apply、bind
call():obj.myFun.call(db,'成都','上海');
apply():obj.myFun.apply(db,['成都','上海']);
bind():obj.myFun.bind(db,'成都','上海')();
console.log(Object.prototype.toString.call(arr) === "[object Array]");
undefined: 未定义的值 。这个值的语义是,希望表示一个变量最原始的状态,而非人为操作的结果。一个变量不赋值就是 undefined
null: 空值 。这个值的语义是,希望表示一个对象被人为的重置为空对象,而非一个变量最原始的状态 。 在内存里的表示就是,栈中的变量没有指向堆中的内存对象。
null值表示一个空对象指针,它代表的其实就是一个空对象
undefined 表示一个变量自然的、最原始的状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。所以,在实际使用过程中,为了保证变量所代表的语义,不要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可****
16、HTTP请求报文、响应报文****
请求头
Accept:告诉服务器,浏览器这边可以处理什么数据
Accept-Encodeing:gzip告诉服务器,浏览器能支持什么样的压缩格式
Cache-control:告诉服务器是否缓存
Host:远程服务器的域名
User-agent:客户端的一些信息,浏览器信息 版本
响应头
cache-control:
content-Enconding:
Date: 时间
server:ngnix 服务器类型
set-Cookie:
17、简述URL输入到网页呈现,发生了什么****
简单版:浏览器向服务器发起请求,服务器根据请求返回内容,浏览器把返回内容解析成页面
详细版:
输入地址:查找浏览器本地缓存,有缓存直接呈现页面,没有则下一步
浏览器查找域名对应IP地址:首先查找本地hosts文件,没有就发送DNS请求到本地DNS服务器,本地DNS没有,本地DNS就会请求根DNS,根DNS会告诉你去哪个域DNS服务器请求。最终得到IP和域名对应关系,本地DNS保存对应关系到缓存,方便下次使用。
浏览器向web服务器发送http请求:拿到IP后,向IP对应的web服务器发起TCP连接请求,经过三次握手建立连接,建立TCP连接后,发起http请求。
服务器返回一个 HTTP 响应:****
浏览器解析HTML文件: 浏览器首先会解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。
18、fetch,axios和ajax的区别****
Ajax : 一种异步调用接口的方式。JQuery ajax 是对原生XHR的封装,除此以外还增添了对JSONP的支持
Axios : ****本质上也是对原生XHR的封装,是用Promise的实现版本,功能更强大。
拦截请求和响应、转换请求和响应数据、取消请求、自动转换JSON数据
Fetch : es6原生方法,用于实现基于promise的http请求,用来取代最早的XMLHttpRequest实现的ajax请求。偏底层,需要封装
19、CommonJS和 ES6 Module区别****
CommonJS:****
Node.js 是 commonJS 规范的主要实践者,实际使用时,用module.exports 定义当前模块对外输出的接口(不推荐直接用 exports),用 require 加载模块
commonJS 用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载
ES6 Module****
export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能
ES6 的模块不是对象,import 命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析(Tree Shaking)成为可能。
静态化的意义:模块的依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,Tree-Shaking 正是基于这个前提才有实现的可能。
区别:****
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”
Import()可是实现异步加载
20、HTTP1.0、2.0、3.0区别****
HTTP1.0
连接无法复用 : 每次发送请求,都需要进行一次TCP连接,而TCP的连接释放过程又是比较费事的。这种无连接的特性会使得网络的利用率变低****
阻塞问题 : 由于HTTP1.0规定下一个请求必须在前一个请求响应到达之前才能发送,假设前一个请求响应一直不到达,那么下一个请求就不发送,后面的请求就阻塞了****
安全问题 :明文传输****
HTTP1.1: 浏览器再也不用为每个请求重新发起TCP连接了。HTTP 1.1引入cookie以及安全机制****
HTTP2.0
加强性能的核心是二进制传输,在HTTP1.x中,我们是通过文本的方式传输数据
多路复用(或连接共享)****
解决了http1.1中的队头阻塞问题,用户体验的感知多数延迟的效果有了量化的改善,以及提升了TCP连接的利用率****
头部压缩****
HTTP3.0
基于google的QUIC协议,而quic协议是使用udp实现的
减少了tcp三次握手时间,以及tls握手时间
优化了重传策略,重传包和原包的编号不同,降低后续重传计算的消耗
21、微前端****
目前的微前端框架一般都具有以下三个特点:
技术栈无关:主框架不限制接入应用的技术栈,子应用具备完全自主权。
独立性强:独立开发、独立部署,子应用仓库独立。
状态隔离:运行时每个子应用之间状态隔离。
实现方案:
Iframe
iframe优点:
iframe 自带的样式、环境隔离机制使得它具备天然的沙盒机制。
嵌入子应用比较简单。
iframe缺点:
iframe功能之间的跳转是无效的,刷新页面无法保存状态。
URL的记录完全无效,刷新会返回首页。
主应用劫持快捷键操作,事件冒泡不穿透到主文档树上。
模态弹窗的背景是无法覆盖到整个应用。
iframe应用加载失败,内容发生错误主应用无法感知,通信麻烦。
single-spa: 是第一个微前端框架,当前流行的大量框架都是single-spa的上层封装,但是如果作为生产选型,single-spa提供的是较为基础的api,应用在实际项目中需要进行大量封装且入侵性强,使用起来不太方便。但是如果想学习相关技术或者封装一套更灵活的解决方法还是很值得使用的 。****
qiankun: 阿里开源的一套框架,基于single-spa的上层封装,社区活跃度较高,在国内的生态较好,中文文档齐全,有大量的先行者铺路,比较适合用于生产环境。****
MicroApp : 借鉴了 WebComponent 的思想,micro-app 通过 CustomElement 结合自定义的 ShadowDom,将微前端封装成一个类 WebComponent 组件,从而实现微前端的组件化渲染
主应用:
import microApp from '@micro-zoe/micro-app'
microApp.start()
添加微应用的容器组件
添加路由指向这个容器组件
子应用:
添加 public-path.js,webpack_public_path
配置端口和跨域
自动切换路由的 basename
22、强制缓存和协商缓存****
强制缓存
1.0 Expires:与本地时间对比,超出了就重新加载
1.1 Cache-Control
· private:客户端可以缓存
· public:客户端和代理服务器均可缓存;
· max-age=xxx:缓存的资源将在 xxx 秒后过期;
· no-cache:需要使用协商缓存来验证是否过期;
· no-store:不可缓存
协商缓存
Last-Modified:服务器设置响应头缓存标识,填入资源最后修改时间
If-Modify-Since:再次请求request的请求头中会包含
服务器判断Last-Modified和If-Modify-Since判断是否缓存
Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识****
If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match (Etag的值)。web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定是否命中协商缓存;
Etag精度要优于Last-Modified,Last-Modified的时间单位是秒、
在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值;
23、深拷贝****
JSON.parse(怕ersi)(JSON.stringify(obj))
只适用于一般数据的拷贝(对象、数组)
时间对象 ( 字符串 )、 RegExp、Error对象 ( 空对象 )、 function,undefined ( 丢失 ) 都有问题
/**
* 判断是否是基本数据类型
* @param value
*/
function isPrimitive(value){
return (typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'symbol' ||
typeof value === 'boolean')
}
/**
* 判断是否是一个js对象
* @param value
*/
function isObject(value){
return Object.prototype.toString.call(value) === "[object Object]"
}
/**
* 深拷贝一个值
* @param value
*/
function cloneDeep(value){
// 记录被拷贝的值,避免循环引用的出现
let memo = {};
function baseClone(value){
let res;
// 如果是基本数据类型,则直接返回
if(isPrimitive(value)){
return value;
// 如果是引用数据类型,我们浅拷贝一个新值来代替原来的值
}else if(Array.isArray(value)){
res = [...value];
}else if(isObject(value)){
res = {...value};
}
// 检测我们浅拷贝的这个对象的属性值有没有是引用数据类型。如果是,则递归拷贝
Reflect.ownKeys(res).forEach(key=>{
if(typeof res[key] === "object" && res[key]!== null){
//此处我们用memo来记录已经被拷贝过的引用地址。以此来解决循环引用的问题
if(memo[res[key]]){
res[key] = memo[res[key]];
}else{
memo[res[key]] = res[key];
res[key] = baseClone(res[key])
}
}
})
return res;
}
return baseClone(value)
}
24、手写instanceof ,手写new****
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left); // 获得原型左边的原型
let prototype = right.prototype; // 获得右边的原型
while(true) {
if(!proto) return false; // 3、原型链的尽头是null 直到最后没有找到的话 说明左右不是一条‘路’的
if(proto === prototype) return true; // 1、如果左右两个原型一样的话 说明右边的构造函数的prototype 在左边实例对象的原型链上
proto = Object.getPrototypeOf(proto); // 2、如果没有比对成功那么继续往上找左边的原型
}
}
function F(){}
let f = new F();
console.log(myInstanceof(f,F)); // true
function mynew(Fun ,...args){
//1.创建一个新的对象
const obj ={};
//2.将新对象的[[prototype]]指向为构造函数的prototype
obj.proto = Fun.prototype;
//3.将构造函数的this设置为新对象
let result = Fun.apply(obj,args);
//4.判断构造函数的返回值
return result instanceof Object? result:obj;
}
TS相关****
对我来说,TypeScript 最大的优点就是其类型推断跟 VS Code 的良好搭配让代码效率有了极大提升
增强代码的可读性和可维护性,强类型的系统相当于最好的文档,在编译时即可发现大部分的错误,增强编辑器的功能
type 和 interface区别****
Type****
写法
type example = {
name: string
age: number
}
继承:通过 & 来实现
type exampleType2 = exampleType1 & {
age: number
}
interface****
写法
interface example {
name: string
age: number
}
继承:通过 extends 实现
interface exampleInterface2 extends exampleType1 {
age: number
}
区别:****
Interface:支持声明合并
interface Info { name: string; }
interface Info { age: number; }
// 实际上的接口Info是
{ name: string; age: number; }
Type可以声明基本类型别名,申明联合类型、元祖等类型
// 基本类型别名
type Name = string;
// 联合类型
type Name = string;
type Age = number;
type Info = Name | Age
/ 元祖类型
type Name = string;
type Age = number;
type Info = [Name , Age]
// 泛型变量
type Callback = (data: T) => void;
as类型断言 is 类型保护
小程序相关****
小程序分包:****
通过subpackages配置数组,数据项内independent:true设置是否独立分包。
独立包与其他包不能互相引用,内存限制不共享(2M)
所有分包总大小不超过20M,单个分包/主包大小不能超过 2M
Vue相关****
生命周期:****
BeforeCreate: 最初调用处发,data和events都不能用。可以在这里处理整个系统加载的Loading;
created:****
beforeMount: 在模板编译后,渲染之前触发。SSR中不可用。基本用不上整个Hook。
mounted: 在渲染之后触发,并可以访问组件中的DOM以及$ref,SSR中不可用。
beforeUpdate: 在数据改变后,模板改变前触发。切勿使用它监听数据变化(使用计算属性和watch监听)。
updated: 在数据改变后、模板改变后触发。常用于渲染后的打点、性能检测或者触发vue组件中非vue组件的更新。
beforeDestroy: 组件卸载前触发,可以在此时清理事件、计时器或者取消订阅操作。
destroyed: 卸载完毕后触发,可以做最后的打点或事件触发操作。
自定义指令:****
Vue.directive(‘name-a’,{
bind:指令与元素成功绑定时调用。
inserted:指令所在元素被插入页面时调用。
update:指令所在模板结构被重新解析时调用。
})
组件间的通信:****
1、props / $emit
父组件通过 props 向子组件传递数据,子组件通过 $emit 和父组件通信
2、ref / $refs
$refs可以找到子组件的实例,并调用子组件内部的方法
3、依赖注入(provide / inject) Vue2.2.0版本新增的****
父组件
provide() {
return {
num: this.num
};
}
子组件
inject: ['num']
依赖注入所提供的属性是非响应式的。
Vue首屏加载优化 :****
1. 路由懒加载****
// 将// import UserDetails from './views/UserDetails'// 替换成
const UserDetails = () => import('./views/UserDetails')
模块变成异步函数,就会在需要的时候才去调用
2. 压缩图片****
cnpm install --save-dev image-webpack-loader
安装image-webpack-loader包,在图片test里引用
{
test: /.(png|jpe?g|gif|webp)(?.*)?$/,
include: [path.resolve('./src')],
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'static/images/[name].[hash:8].[ext]'
}
}
}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: { progressive: true, quality: 50 }, // 压缩JPEG图像
optipng: { enabled: true }, // 压缩PNG图像
pngquant: { quality: [0.5, 0.65], speed: 4 }, // 压缩PNG图像
gifsicle: { interlaced: false }, // 压缩GIF图像
webp: {
quality: 100
}
}
}
]
}
3. Gzip压缩****
Ngnix http{ gzip_static on; }
Webpack4安装npm i compression-webpack-plugin@5.0.1
plugins: [
new CompressionWebpackPlugin({
filename: '[path].gz[query]', // 压缩后的文件名(保持原文件名,后缀加.gz)
algorithm: 'gzip', // 使用gzip压缩
test: /.(js)$/i,
threshold: 10240 // 对超过10k的数据压缩
})
]
4. Webpack包体积优化****
直接引用const { CleanWebpackPlugin } = require('clean-webpack-plugin');
//通用配置****
// 表示选择哪些 chunks 进行分割,可选值有:async,initial和all, 默认:async
chunks: "all",
// 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。 minSize: 3000,
// 缓存组: cacheGroups 的配置项跟 splitChunks是一样的, 但是它自己有几个自己的配置项
cacheGroups: {
//各个模块个性化配置****
vender: {
// 优先级:数字越大优先级越高,因为默认值为0,所以自定义的一般是负数形式
priority: -10,
// test:可以是一个函数也可以是一个正则,函数的返回值是:boolean RegExp string,通过返回值或者正则来进行匹配。
test: /[\/]node_modules[\/]/,
},
}
// splitChunks可视化插件
const Bundle=require("webpack-bundle-analyzer").BundleAnalyzerPlugin;****
Plugin:[
New Bundle()
]
watched和computed的区别****
watched监听属性 computed 计算属性****
Computed: 设置一个值,依赖的响应式属性变化就会重新计算
Watched: 一个对象,键是需要观察的表达式,值是对应回调函数,键发生变化触发回调
官方文档:对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性****
当需要在数据变化时执行异步或开销较大的操作时,watch侦听器****
Computed****
data 属性初始化 getter setter
2. computed 计算属性初始化,提供的函数将用作属性 vm.(Computed里的属性)的 getter
3. 当首次获取 (Computed里的属性)计算属性的值时,Dep 开始依赖收集
4. 在执行 (data里的值) getter 方法时,如果 Dep 处于依赖收集状态,则判定 (data里的值)为 (Computed里的属性)的依赖,并建立依赖关系5. 当 (data里的值)发生变化时,根据依赖关系,触发 (Computed里的属性)的重新计算
Vuex****
vue 的状态管理工具 管理项目中的公共数据 能够在所有的组件中使用
s tate : 存储数据,存储状态
在根实例注册 store 后,用this.$store.state 来访问
存储数据的方式为响应式,vue组件从store中读取数据,如数据发生变化,组件也会更新
g etter : store 的计算属性,它的返回值会根据它的依赖被缓存起来,并且只要它的依赖发生变化才会被重新计算
//vuex里设置
getters: {
doneTodos (state,getters) {
//getters本身
return state.todos.filter(todo => todo.done)
}
}
getters: {
// get传参id,查询
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}}
//外部调用
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}}
mapGetters ****辅助函数仅仅是将 store 中的 getter 映射到局部计算属性
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
doneCount: 'doneTodosCount',
'doneTodosCount',
'anotherGetter',
// ...
])
}}
m utation : 更改 vue 中 store 状态的唯一方法就是 mutation
同步方法,提交更新数据的方法 this.$store.commit ()
//vuex里设置
mutations: {
increment (state) {
state.count++
}
}
//外部调用
this.$store.commit('increment')
Action : Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作
//vuex里设置
actions: {
increment (context) {
context.commit('increment')
}或者
increment ({ dispatch,commit }) {
return new Promise((resolve, reject) => {
commit('increment')
resolve()
}
}
}
//外部调用
this.$store.dispatch('increment').then(()=>{})
Module :****
const moduleA = {
// 命名空间
namespaced: true,
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { isAdmin () { ... }} // getters[a/isAdmin']
}
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}})
store.state.a // -> moduleA 的状态
defineProperty和Proxy的区别****
监听数据角度
defineproperty只能监听某个属性而不能监听整个对象
proxy不用设置具体属性,直接监听整个对象
defineproperty监听需要知道是哪个对象的哪个属性,而proxy只需要知道哪个对象就可以了。也就是会省去for in循环提高了效率
监听对原对象的影响
因为defineproperty是通过在原对象身上新增或修改属性增加描述符的方式实现的监听效果,一定会修改原数据。
而proxy只是原对象的代理,proxy会返回一个代理对象不会在原对象上进行改动,对原数据无污染
实现对数组的监听
defineproperty无法监听数组长度变化, Vue只能通过重写数组方法的方式变现达成监听的效果,光重写数组方法还是不能解决修改数组下标时监听的问题,只能再使用自定义的$set的方式
而proxy因为自身特性,是创建新的代理对象而不是在原数据身上监听属性,对代理对象进行操作时,所有的操作都会被捕捉,包括数组的方法和length操作,再不需要重写数组方法和自定义set函数了。(代码示例在下方)
监听的范围
defineproperty只能监听到value的 get set 变化
proxy可以监听除 [[getOwnPropertyNames]] 以外所有JS的对象操作
nextTick的原理和使用?****
使用场景:****
数据更新后想要马上操作新的DOM,需要把操作写在nextTick的回调里****
在created钩子函数里需要操作DOM,也可以把操作写在nextTick的回调里,(created钩子函数里还没有挂载dom,所以直接操作会有问
原理:****
Vue的Dom更新是异步的,数据修改不会马上体现,所以需要nextTick。
Vue会在事件循环队列中加入一个回调,确保Dom操作完成后才调用
当我们自己调用 nextTick 的时候,它就在更新 DOM 的那个 microtask 后追加了我们自己的 回调函数,从而确保我们的代码在 DOM 更新后执行
1、vue用异步队列的方式来控制DOM更新和nextTick回调先后执行
2、 microtask(微任务)因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
3、因为兼容性问题,vue不得不做了microtask(微任务)向macrotask(宏任务)的降级方案
Vue常用修饰符****
v-model.trim="value" //过滤掉输入内容的前后空格
@click.stop="" // 阻止了事件冒泡,相当于调用了event.stopPropagation方法
Vue模板编译成render函数的过程Compile****
模板解析阶段:转换成AST抽象语法树
优化阶段:优化器(标记静态节点:文本)
代码生成阶段:代码生成器
输出渲染函数
Vue响应式原理****
Object.defineProperty()监听对象属性的改变
vue实现数据响应式,是通过数据劫持侦测数据变化,发布订阅模式进行依赖收集与视图更新,换句话说是Observe,Watcher以及Compile三者相互配合,
Observe通过Object.defineProperty拦截data属性的setter/getter方法,从而使每个属性都拥有一个Dep,当触发getter时收集依赖(使用该属性的watcher),当触发setter时通知更新;
Dep: 依赖收集器,用于维护依赖data属性的所有Watcher;
Dep一方面用数组收集与属性相关的Watcher,另一方面遍历数组通知每个Watcher进行update。
Watcher: 将视图依赖的属性绑定到Dep中,当数据修改时触发setter,调用Dep的notify方法,通知所有依赖该属性的Watcher进行update更新视图,使属性值与视图绑定起来
Compile模板指令解析器,对模板每个元素节点的指令进行扫描解析,根据指令模板替换属性数据,同时注入Watcher更新数据的回调方法
每个组件实例都对应一个watcher实例,它会在组件渲染的过程中访问过的属性设置为依赖。之后当属性的setter触发时,会通知watcher对关联的组件进行重新渲染。
Vue性能优化****
Object.freeze()冻结一个对象,对于商品列表等静态展示,不需要做响应式处理
列表使用唯一的key,非index
频繁改变切换用v-show,v-if为false,不会加载,看情况使用
纯展示,没有响应式的组件设置为函数组件<template functional>会在渲染时开销低很多
第三方插件按需引入、图片懒加载
Vue各种实现原理****
VueComponent.prototype.proto === Vue.prototype(VC显示原型属性指向实例隐式原型属性)
Keeplive实现原理****
组件可以接收三个属性:
· include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
· exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
· max - 数字。最多可以缓存多少组件实例。
created钩子会创建一个cache对象,用来作为缓存容器,保存vnode节点。
destroyed钩子则在组件被销毁的时候清除cache缓存中的所有组件实例。
render()函数里获取到keeplive插槽内容,
获取组件节点名称或者tag,与include、exclude匹配
然后到this.cache里去比对,命中缓存就从缓存里取,删掉放到keys最后一个
为什么要删除第一个缓存组件并且为什么命中缓存了还要调整组件key的顺序?
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
组件一旦被 缓存,那么再次渲染的时候就不会执行 created、mounted 等钩子函数。使用keepalive组件后,被缓存的组件生命周期会多activated和deactivated 两个钩子函数,它们的执行时机分别是 包裹的组件激活时调用和停用时调用
Vuex实现原理****
判断若处于浏览器环境下且加载过Vue,则执行install方法****
if(typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
将全局vue对象赋值给vue局部变量,再调用applyMixin(Vue)
在beforeCreated钩子前插入vuex初始化代码,就是在初始化vue根组件时,把store设置到$store上
在每个组件都能通过this.$store调用
单元测试****
npm install --save-dev jest @vue/test-utils
然后我们需要在 package.json 中定义一个单元测试的脚本
// package.json{
"scripts": {
"test": "jest"
}}
为了告诉 Jest 如何处理 *.vue 文件,我们需要安装和配置 vue-jest 预处理器
"jest": {
"moduleFileExtensions": [
"js",
"json",
// 告诉 Jest 处理 *.vue 文件
"vue"
],
"transform": {
// 用 vue-jest 处理 *.vue 文件
".*\.(vue)$": "vue-jest"
}
}
// test.js
// 从测试实用工具集中导入 mount() 方法
// 同时导入你要测试的组件
import { mount } from '@vue/test-utils'
import Counter from './counter'
// 现在挂载组件,你便得到了这个包裹器
const wrapper = mount(Counter)
// 你可以通过 wrapper.vm 访问实际的 Vue 实例
const vm = wrapper.vm
describe('Counter', () => {
// 现在挂载组件,你便得到了这个包裹器
const wrapper = mount(Counter)
it('renders the correct markup', () => {
expect(wrapper.html()).toContain('0')
})
// 也便于检查已存在的元素
it('has a button', () => {
expect(wrapper.contains('button')).toBe(true)
})})
wrapper .find('button')****
button .trigger('click')****
React 相关****
说说对React的理解?有哪些特性?****
组件间的组合和嵌套构成整体页面,JXS语法,单向数据绑定,虚拟Dom,组件化
JSX:
首字母大写,和html元素区分开。驼峰命名
嵌套,render函数return一个顶层标签,Fragment < >< />
{}里为js规则,class改成className
类组件和函数组件之间有什么区别?****
类组件:****
运用es6的class类语法,业务逻辑复杂,用类组件更易于我们维护,也相应降低了开发成本
class MyComponents extends React.Component
需要constructor(props)来接受父组件参数this.props
state状态存储,通过this.setState()设置内部值
constructor(props){
super(props)
this.state = {
a:1
}
}
有生命周期函数
constructor() 初始化state
shouldComponentUpdate() return false阻止更新,用React.PureComponent 可代替****
componentDidMount 组件已出现在页面
getDerivedStateFromProps() render 方法之前调用,并且在初始挂载及后续更新时都会被调用
getSnapshotBeforeUpdate()在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()
componentDidUpdate() 组件已更新
componentWillUnmount() 组件从dom移除
类组件性能消耗大,需要创建实例,不能销毁
函数组件: 用于简单功能模块封装,便于复用****
通过hook保存数据状态,消耗小,直接执行return的返回
interface Props {
name: string;
}****
const MyComponents: React.FC = (props)=> {
return
Hello Word!{props.name}
;} //不支持泛型,或删除React.FC
const App = ({ message }: { message: string }) =>
虚拟Dom的理解****
本质上是js的对象
· state 变化,生成新的 VD
· 比较 VD 与之现有dom的异同
· 生成差异的同时更新 DOM
靠 setState 触发渲染, setState 是异步的 表现形式 ,多次 setState 调用会合并****
React hook里,截流防抖不生效?****
因为函数组件每次渲染结束之后,内部的变量都会被释放,重新渲染时所有的变量会被重新初始化,产生的结果就是每一次都注册和执行了setTimeout函数。想要得到正确的运行结果,必须以某种方式存储那些本会被删除的变量和方法的引用。
通过自定义Hook组件去解决这个问题
React.memo(A)****
高阶组件,接受一个组件,然后经过一些判断和处理后再返回这个组件,浅层比较
接收一个组件A作为参数并返回一个组件B,如果组件B的props没有改变,则组件B会阻止组件A重新渲染。A和B本质上是同一个组件,但A是否进行重新渲染,需要由Props是否发生改变来决定。
传入值不变,就不会重新渲染,避免子组件不必要的渲染
当我们的父子组件之间不需要传值通信时,可以选择用React.memo来避免子组件的无效重复渲染。
但我们的父子组件之间需要进行传值通信时,React.memo和useMemo都可以使用
useMemo****
React提供的一个hook,使用useMemo定义的变量,只会在useMemo的第二个依赖参数发生修改时才会发生修改
我们使用useMemo时,应保证第一个参数函数里所使用的变量都出现在第二个依赖参数数组中,这样可以避免一些额外的错误
解决因函数更新而渲染自己的问题,就可以使用useMemo,使用它将函数重新封装****
如果是组件中有复杂计算的function,应该使用usememo而不是useCallback。因为useCallback缓存函数的引用,useMemo缓存计算数据的值。useMemo是避免在每次渲染时都进行高开销的计算的优化的策略.
useCallback****
const add2= useCallback(() => {
setNum(num+ 1);
}, [num])
接收一个函数和比对值 ,避免函数不必要的执行
useCallback的作用:配合memo用于优化子组件的渲染次数
useCallback 对于子组件渲染优化
useMemo 对于当前组件高开销的计算优化
Vue和React区别****
区别:vue是双向绑定的,采用template;react是单向的,采用jsx
监听数据变化的实现原理不同
Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化。
React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。为什么React不精确监听数据变化呢?这是因为Vue和React设计理念上的区别,Vue使用的是可变数据,而React更强调数据的不可变,两者没有好坏之分,Vue更加简单,而React构建大型应用的时候更加鲁棒。
框架本质不同
Vue本质是MVVM框架,由MVC发展而来;
React是前端组件化框架,由后端组件化发展而来。
Vue 自动挡 react 手动挡
1、vue里有插槽的概念,react是jsx语法,任何东西都作为props传递
2、React的Context.Provider和Vue的provide()inject()写法不同,效果相同
3、Vue Diff算法左右一起比对,react从左到右。。。
Vue和React相似****
1、React.createRef() 类似 Vue3 ref(null)****
2、2、forceUpdate()强制组件重新渲染函数,都有****
Vue3相关****
1、源码模块拆分更加细化,分包更细,减小引用包的体积。
2、Vue3全面转向TS,对ts有更好的支持
3、Vue3区分纯静态元素和动态元素,diff运算时只比较动态元素,提升了性能
4、new Proxy方法监听对象改变,实现响应式
5、Options api ==》 Componsition api(函数式组件)
Ref和reactive****
reactive是用来定义更加复杂的数据类型,但是定义后里面的变量取出来就不在是响应式Ref对象数据了,所以需要用toRefs函数转化为响应式数据对象
ref本质上还是reactive,只不过reactive需要传入一个对象,但是有时候我们需要一个基本类型作为响应式,如果用reactive的话就需要reactive({value:xxx})这样传进去,所以vue就提供了ref这种写法,让你不需要手动包装成对象。所以这也是为什么ref包装的响应式对象需要.value才能拿到值的原因。
Webpack相关****
ModuleFederationPlugin插件,导出单独模块,实现共享
// 暴露组件配置
new ModuleFederationPlugin({
name: 'app2',
library: { type: 'var', name: 'app2' },
filename: 'remoteEntry.js', // 生成的文件名
exposes: {
'./Button': './src/Button', // Export Button 组件
}, // 共享 react 和 react-dom
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
}),
// 接受组件配置
plugins: [
new ModuleFederationPlugin({
name: 'app1',
remotes: {
// http://localhost:3002/remoteEntry.js
// 上面配置生成的模块文件
app2:app2@${getRemoteEntryUrl(3002)},
}, // 共享模块
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
}),
],
loader: webpack本身只能打包Javascript文件,对于其他资源例如 css,图片,或者其他的语法集比如jsx,是没有办法加载的。 这就需要对应的loader将资源转化,加载进来
css-loader、babel-loader
****
plugin****
使用plugin丰富的自定义API,可以控制webpack编译流程的每个环节,实现对webpack的自定义功能扩展。
plugin是一个具有 apply方法的 js对象。apply方法会被 webpack的 compiler(编译器)对象调用,并且 compiler 对象可在整个 compilation(编译)生命周期内访问。
Babel
Babel会将ES6的代码转成ES5的代码
@babel/core
babel-loader
@babel/preset-env
需要先安装,在配置
(1)读取入口文件,如项目中的main.js;
(2)由入口文件,解析模块所依赖的文件或包,生成ATS树;
(3)对模块代码进行处理:根据@babel工具转换ATS树(es6转es5、polyfill等);
(4)递归所有模块
(5)生成浏览器可运行的代码
浏览器兼容相关****
样式需要重置,每个浏览器默认样式不同
Input type=“datetime-local”,每个浏览器表现形式不同
移动端300ms延迟****
移动端要判断是否双击,所以点击完有300s延迟。
Css:touch-action: none 取消元素默认行为
Css相关****
Div垂直居中:****
1、flex、justify-content:center、align-items:center
2、Margin:1/2长 1/2高;position:absolute;top:50%;left:50%
CSS样式选择器优先级****
优先级关系:内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器
1.css选择规则的权值不同时,权值高的优先;
2.css选择规则的权值相同时,后定义的规则优先;
- css属性后面加 !important 时,无条件绝对优先;
!important可用transform和max-width这类样式替换****
层叠上下文****
position不是static的同级元素元素,z-index谁大谁在上面****
css3中:
父元素的display属性值为flex|inline-flex,子元素z-index属性值不为auto的时候,子元素为层叠上下文元素
元素的opacity属性值不是1
元素的transform属性值不是none;
Margin****
第一个元素margin-top会让父元素有margin****
一个盒子如果没有上补白(padding-top)和上边框(border-top),那么这个盒子的上边距会和其内部文档流中的第一个子元素的上边距重叠
父元素设置 border 或者 overflow:hidden 解决****
Margin为负数****
父级设置了border,父 级div的height = 子元素的margin-top + margin-bottom + height + padding-top + padding-bottom****
不设置border和padding,那么父元素的height是不计算子元素的上下外边距的****
leetcode相关****
1、 编写一个函数来查找字符串数组中的最长公共前缀。****
如果不存在公共前缀,返回空字符串 ""。****
2、将'a.b.c.d'转换成{a:{b:{c:{d:{}}}}},console.log(JSON.stringify(transform('a.b.c.d')))****
function transform(data){****
let arr = data.split('.')
let returnObj = {}
for(let i = arr.length-1;i>=0;i--){
****let obj = {}
****obj[arr[i]] = returnObj
****returnObj = obj
}
return returnObj
}****
console.log(JSON.stringify(transform('a.b.c.d')))****
3、矩阵螺旋遍历****
4、
/**
* @param {number[][]} matrix
* @return {number[]}
*/
var spiralOrder = function(matrix) {
var returnArr = []
var rows = matrix.length //行数
var column = matrix[0].length//列数
var left = 0,right = column-1,top = 0,bottom = rows-1
while(left <= right && top <= bottom){
for(let i=left;i<=right;i++){
returnArr.push(matrix[top][i])
}
for(let j=top+1;j<=bottom;j++){
returnArr.push(matrix[j][right])
}
if (left < right && top < bottom) {
for(let ii = right-1;ii>=left;ii--){
returnArr.push(matrix[bottom][ii])
}
for(let jj = bottom-1;jj>top;jj--){
returnArr.push(matrix[jj][left])
}
}
[left,right,top,bottom] = [left+1,right-1,top+1,bottom-1]
}
return returnArr
};
对比两数组差异
function getArrDifference(arr1, arr2) {
return arr1.concat(arr2).filter(function (v, i, arr) {
return arr.indexOf(v) === arr.lastIndexOf(v);
});
}
最长公共子序列
/**
* @param {string} text1
* @param {string} text2
* @return {number}
*/
var longestCommonSubsequence = function(text1, text2) {
let row=text1.length,col=text2.length
let dp = new Array(row+1).fill(0).map(item=>new Array(col+1).fill(0))
for(let i = 1;i<=row;i++){
for(let j = 1;j<=col;j++){
if(text1[i-1] === text2[j-1]){
dp[i][j] = dp[i-1][j-1]+1
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1])
}
}
}
return dp[row][col]
};