ES6
新特性:let、const:变量声明拥有块作用域,不会变量提升,但会暂时性死区。
解构赋值:const a = {...obj, val1: 1, val2: 2}
即扩展运算符…
class关键字:实现类、对象继承。super、constructor、extends等。
模板字符串、函数参数默认值、Symbol基本数据类型
Map、和对象区别:key可以为任意值,对象key必须为string或symbol。Map.set(key,value) Map.get(key)
**map方法和forEach
区别:**map方法返回一个新数组,对遍历数据执行函数可进行改变,forEach只是对每个数据进行遍历执行不会改变数据。 **Array.form
**可以将Map转化为Array类型
箭头函数()=>{}
:this执行为上一层,而不是其调用者。
Promise
ESModule的新增:import、export
function fn(){}
js会将其提升,在其定义之前就可以使用。var fn = function(){}
js会将fn函数视为变量,提升值先为undefined,若调用则会出错。重绘Repaint
:盒模型已经布局好,对其进行绘制的过程。在改变元素外观时进行,outline、颜色背景色。
回流Reflow
:布局引擎根据所有的样式计算出盒模型在页面的位置和大小,计算元素位置和大小时进行。
重绘不一定回流、回流一定重绘!
触发重绘属性:(与元素颜色背景有关)
* color * background * outline-color
* border-style * background-image * outline
* border-radius * background-position * outline-style
* visibility * background-repeat * outline-width
* text-decoration * background-size * box-shadow
触发回流属性:(基本与位置大小有关)
* width * top * text-align-*
* height * bottom * overflow-y
* padding * left * font-weight
* margin * right * overflow
* display * position * font-family
* border-width * float * line-height
* border * clear * vertival-align
* min-height * white-space
回流比重绘成本高,修改CSS
样式、增删改DOM节点、移动DOM都会触发回流,获取一些需要计算的属性(offsetTop、clientHeight等)也会触发回流。
优化方案
transform:translate
代替left、top的操作。transform:translateZ(0)
开启GPU
硬件加速,复杂的动画先使用position:absolute/fixed
使其脱离文档流后再操作。typeof
:返回字符串,表示类型。(string、null(object)、undefined、symbol、number、function、bigint、boolean)对基本类型判断准确,无法精确判断引用数据类型(null和Array都会识别为object)instanceof
:检测构造函数prototype是否出现在某个实例对象的原型链上。对引用数据类型判断准确,无法对基本数据类型精确判断,限制数据必须是new出来的对象。constructor
:**每个函数定义都会生成一个constructor,基本类型会隐式装箱,创建构造函数实例。可以对基本数据类型和引用数据类型精确判断,但无法识别null和undefined。Object.prototype.toString.call()
:可以对任何数据类型进行判断,生成结果为[object type]
(缺点是自身可能被修改)
ESModule
和CommonJS
区别流体布局float:左右浮动,中间margin控制距离。
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>两栏布局title>
<style>
* {
margin: 0;
padding: 0;
text-align: center;
}
header {
background-color: #ddd;
}
.content {
width: 100%;
}
.left {
float: left;
width: 200px;
background-color: antiquewhite;
}
.right {
float: right;
width: 200px;
background-color: aquamarine;
}
.mid {
margin: 0 200px;
background-color: skyblue;
}
footer {
background-color: #ddd;
}
style>
head>
<body>
<header>headerheader>
<div class="content">
<div class="left column">leftdiv>
<div class="right column">rightdiv>
<div class="mid column">middiv>
div>
<footer>footerfooter>
body>
html>
BFC
布局:左右浮动,利用BFC
元素不会和浮动元素重合特性。
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>两栏布局title>
<style>
* {
margin: 0;
padding: 0;
text-align: center;
}
header {
background-color: #ddd;
}
.content {
width: 100%;
}
.left {
float: left;
width: 200px;
background-color: antiquewhite;
}
.right {
float: right;
width: 200px;
background-color: aquamarine;
}
.mid {
overflow: hidden;
/* 或者使用 display: flex; */
background-color: skyblue;
}
footer {
background-color: #ddd;
}
style>
head>
<body>
<header>headerheader>
<div class="content">
<div class="left column">leftdiv>
<div class="right column">rightdiv>
<div class="mid column">middiv>
div>
<footer>footerfooter>
body>
html>
flex
布局:父元素flex布局,mid子元素为flex-grow:1
填充。
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>两栏布局title>
<style>
* {
margin: 0;
padding: 0;
text-align: center;
}
header {
background-color: #ddd;
}
.content {
width: 100%;
display: flex;
}
.left {
width: 200px;
background-color: antiquewhite;
}
.right {
width: 200px;
background-color: aquamarine;
}
.mid {
flex: 1;
/* 即flex-grow: 1; */
background-color: skyblue;
}
footer {
background-color: #ddd;
}
style>
head>
<body>
<header>headerheader>
<div class="content">
<div class="left column">leftdiv>
<div class="mid column">middiv>
<div class="right column">rightdiv>
div>
<footer>footerfooter>
body>
html>
Tabel
布局:父元素table布局,子元素设置为table-cell单元格形式。
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>两栏布局title>
<style>
* {
margin: 0;
padding: 0;
text-align: center;
}
header {
background-color: #ddd;
}
.content {
width: 100%;
display: table;
}
.column {
display: table-cell;
}
.left {
width: 200px;
background-color: antiquewhite;
}
.right {
width: 200px;
background-color: aquamarine;
}
.mid {
background-color: skyblue;
}
footer {
background-color: #ddd;
}
style>
head>
<body>
<header>headerheader>
<div class="content">
<div class="left column">leftdiv>
<div class="mid column">middiv>
<div class="right column">rightdiv>
div>
<footer>footerfooter>
body>
html>
定位:左右栏绝对定位脱标,中间部分设置边距挤入。
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>两栏布局title>
<style>
* {
margin: 0;
padding: 0;
text-align: center;
}
header {
background-color: #ddd;
}
.content {
position: relative;
width: 100%;
}
.left {
position: absolute;
top: 0;
left: 0;
width: 200px;
background-color: antiquewhite;
}
.right {
position: absolute;
top: 0;
right: 0;
width: 200px;
background-color: aquamarine;
}
.mid {
margin: 0 200px;
background-color: skyblue;
}
footer {
background-color: #ddd;
}
style>
head>
<body>
<header>headerheader>
<div class="content">
<div class="left column">leftdiv>
<div class="mid column">middiv>
<div class="right column">rightdiv>
div>
<footer>footerfooter>
body>
html>
圣杯布局:利用浮动、定位、margin负值。
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>两栏布局title>
<style>
* {
margin: 0;
padding: 0;
text-align: center;
}
header {
background-color: #ddd;
}
.content {
margin: 0 200px;
}
.left {
position: relative;
left: -200px;
float: left;
margin-left: -100%;
width: 200px;
background-color: antiquewhite;
}
.right {
position: relative;
right: -200px;
float: left;
margin-left: -200px;
width: 200px;
background-color: aquamarine;
}
.mid {
float: left;
width: 100%;
background-color: skyblue;
}
footer {
clear: both;
background-color: #ddd;
}
style>
head>
<body>
<header>headerheader>
<div class="content">
<div class="mid column">middiv>
<div class="left column">leftdiv>
<div class="right column">rightdiv>
div>
<footer>footerfooter>
body>
html>
双飞翼布局:与圣杯布局基本一致,圣杯布局是父元素空出位置给left、right定位占用。而双飞翼布局则是通过修改mid中内容的左右margin使得left、right占用空隙。
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>两栏布局title>
<style>
* {
margin: 0;
padding: 0;
text-align: center;
}
header {
background-color: #ddd;
}
.column {
float: left;
}
.left {
margin-left: -100%;
width: 200px;
background-color: antiquewhite;
}
.right {
margin-left: -200px;
width: 200px;
background-color: aquamarine;
}
.mid {
width: 100%;
background-color: skyblue;
}
#mid-content {
margin: 0 200px;
}
footer {
clear: both;
background-color: #ddd;
}
style>
head>
<body>
<header>headerheader>
<div class="content">
<div class="mid column">
<div id="mid-content">middiv>
div>
<div class="left column">leftdiv>
<div class="right column">rightdiv>
div>
<footer>footerfooter>
body>
html>
CSS3
新特性border-radius:圆角边框
box-shadow:边框阴影(参数:X轴偏移量、Y轴偏移量、阴影模糊半径、阴影扩展半径、颜色、投影方式[ inset为内部投影,省略则为外部 ])
border-image:边框图片,对图片进行切割。
rgba:rgb颜色和opacity透明度
liner-gradient:线性渐变色(参数:渐变方向、起始点颜色、终止点颜色)
text-overflow:文字溢出(clip剪切、ellipsis省略号)word-wrap:文字排版(nowrap强制一行)
text-shadow:文字阴影
background-origin:图片起始位置(border-box、padding-box、content-box)只有在background-image设置为no-repeat时生效。
rotate旋转、scale变形
盒子模型: box-sizing:content-box
(标准盒模型,宽度高度等于border+padding+content) border-box
(怪异盒模型,宽度高度包含了padding、border,即会撑开盒子)
**flex布局:**flex简写属性->flex-grow、flex-shrink、flex-basics
解决方案:
JSONP
(只能进行get请求)利用script没用跨域限制,设置src属性发送带有回调函数参数的GET请求。
//前端请求
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res));
}
//nodejs实现后端
var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {
var params = querystring.parse(req.url.split('?')[1]);
var fn = params.callback;
// jsonp返回设置
res.writeHead(200, { 'Content-Type': 'text/javascript' });
res.write(fn + '(' + JSON.stringify(params) + ')');
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
CORS
跨域资源共享:服务端设置Access-Control-Allow-Origin
//前端若携带cookie
//原生ajax
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
//jQuery
$.ajax({
xhrFields: {
withCredentials: true,
},
crossDomain: true
})
//vue
axios.defaults.withCredentials = true
proxy
:vue处理跨域
module.exports = {
devServer: {
historyApiFailback: true,
proxy: [{
content: '/login',
target: 'http://www.xxx.com:3007',
changeOrigin: true,
secure: false,
cookieDomain.Rewrite: false
}]
}
}
基于websocket
的socket.io
<div>user input:<input type="text"></div>
<script src="./socket.io.js"></script>
<script>
var socket = io('http://www.domain2.com:8080');
// 连接成功处理
socket.on('connect', function() {
// 监听服务端消息
socket.on('message', function(msg) {
console.log('data from server: ---> ' + msg);
});
// 监听服务端关闭
socket.on('disconnect', function() {
console.log('Server socket has closed.');
});
});
document.getElementsByTagName('input')[0].onblur = function() {
socket.send(this.value);
};
</script>
CSS
三角对不设置高度宽度的盒子设置border达到三角,一处为有色、两侧为透明、对应一处为0
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
<style>
* {
margin: 0;
padding: 0;
}
.triangle {
margin: 100px auto;
border-top: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-bottom: 20px solid skyblue;
width: 0;
height: 0;
}
style>
head>
<body>
<div class="triangle">div>
body>
html>
0.5px
的线box-shadow实现:阴影模糊扩展半径设置为0.5px
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>0.5pxtitle>
<style>
div {
width: 200px;
height: 200px;
background-color: skyblue;
margin: 200px auto;
box-shadow: 0 0 0 0.5px #000;
}
style>
head>
<body>
<div>div>
body>
html>
::after伪类实现:为盒子设置after伪元素,宽度为盒子宽度,高度为0.5px。
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>0.5pxtitle>
<style>
div {
position: relative;
width: 200px;
height: 200px;
background-color: skyblue;
margin: 200px auto;
}
div::after {
position: absolute;
content: "";
bottom: 0;
width: 100%;
height: 0.5px;
background-color: #000;
}
style>
head>
<body>
<div>div>
body>
html>
transform缩放实现:伪类元素配合scale缩放属性,初始为盒子两倍大小再缩放为等比。
DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>0.5pxtitle>
<style>
div {
position: relative;
width: 200px;
height: 200px;
background-color: skyblue;
margin: 200px auto;
}
div::after {
position: absolute;
top: -50%;
left: -50%;
content: "";
width: 200%;
height: 200%;
border-bottom: 1px solid #000;
transform: scale(.5, .5);
}
/* 这里也可以使用 transform-origin设置伪类元素变化原点为盒子左上角 */
div::after {
position: absolute;
content: "";
width: 200%;
height: 200%;
border-bottom: 1px solid #000;
transform: scale(.5, .5);
transform-origin: 0 0; /* 相当于top left */
}
style>
head>
<body>
<div>div>
body>
html>
4kb
**。通过setMaxAge
设置cookie有效期,浏览器访问服务器会在响应头中添加set-cookie字段用于将cookie返回给浏览器。sessionID
,为了防止溢出,每个session都有有效期(30min)
,有效期内未访问则判定用户离线删除该session。服务器端的session和客户端的cookie息息相关,通常sessionID
存放在cookie中,当第一次请求时会创建session对象,将sessionID
放入响应头的set-cookie中,下次请求cookie就会携带sessionID
发送至服务器。sessionID
到服务端匹配session信息)
webStorage
类似于cookie,是浏览器本地存储的新方式,存储量更大,且不会像cookie一样请求一次就会携带一次。localStorage
:生命周期永久,除非手动去除,存储大小为5MB,浏览器在隐私模式下不可被读取,不能被爬虫。sessionStorage
:在同源的窗口中始终会存在,只要浏览器不关闭就不会删除。webStorage
是以字符串形式进行存储,存储对象需要用到JSON.stringify
和JSON.parse
localStorage
和cookie在同源窗口中共享,sessionStorage
不在不同的浏览器窗口中共享)vue
中,模板会被解析成抽象语法树AST
,然后被转化为渲染函数,最终渲染为DOM。vue
中的模板编译器将模板解析成一个AST
、AST
是由一系列节点组成的树形结构。节点为vue中的DOM、指令、文本等。模板编译器将模板中的元素区分存储在对象中,最后将这些节点转化为VNode
(虚拟节点)树。vue
通过比较新旧VNode
树,进行高效更新,是响应式渲染的核心所在。vue
中对组件进行状态属性修改时,组件不会立即更新,而是会再下一个事件循环中更新,由于vue更新是异步的这样做可以节省性能。nextTick
接收回调函数作为参数,回调函数在下一次事件循环中执行,且可以获取到更新后的DOM,且nextTick
会返回一个Promise对象,可以使用async
、await
进行取值。prototype
其指向该函数的原型对象。__proto__
属性,其指向这个对象的原型对象。__proto__
属性,其指向这个对象继承的父对象的实例化对象,这样不断寻找的过程即为原型链。最终会找到Object、Object的原型对象最终指向null。Vue2
响应式原理vue2
实现响应式核心是**Object.defineProperty
**方法.
通过内部get,set的监听来进行**数据劫持,结合发布订阅模式,**在数据发生改变的时候,发送消息给订阅者,触发对应的监听回调渲染视图,即数据和视图同步变化。
缺点:新增删除属性不会更新,需要使用$set
和$delete
语法糖操作,对数组修改不会更新,尽量使用splice
。
const data = {name: 'lwh', age: 18}
observer(data)
function observer(target) {
if (typeof target !== 'object' || target == null) {
return target
}
for (let key in target) {
defineReactive(target, key, target[key])
}
}
function defineReactive(target, key, value) {
Object.defineProperty(target, key, {
get: function () {
return value
},
set: function (newValue) {
if (value !== newValue) {
// 视图更新
value = newValue
}
}
})
}
若对象是多层的则需要深度监听(在进行数据劫持时先进行一次获取,在调用set时再进行一次获取)
function defineReactive(target, key, value) {
observer(value) // 深度监听
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
observer(newValue)
if(newValue !== value) value = newValue // 视图更新
}
})
}
对observer的优化,将其封装为类对象:
//检测数据
function observe(data) {
if(typeof data !== 'object') return
new Observer(data)
}
class Observer {
constructor(value) {
this.value = value
this.walk()
}
walk() {
Object.keys(value).forEach(e => defineReactive(value, e, value[e]))
}
}
依赖:
通过Watcher类和Dep类进行响应式实现。Wathcer类为中介,数据变化时通过Watcher中转,通知组件。Dep类用于管理依赖,每个Observer类的实例都拥有一个Dep实例。通过dep.depend收集、dep.notify进行通知。
vue中的Watcher实例订阅一个或者多个数据,这些数据称为Watcher的依赖,依赖发生变化,会执行Watcher内部的回调cb实现某些功能。
在getter中收集依赖、在setter中触发依赖。
只有Watcher触发的getter才会收集到依赖,触发了getter的Watcher会被收集到Dep中。
class Watcher {
constructor(data, expression, cb) {
//data:数据对象
//expression:表达式,和data配合获取watcher依赖的数据
//cb:依赖变化时触发的回调
this.data = data
this.expression = expression
this.cb = cb
this.value = this.get()
},
get() {
const value = parsePath(this.data, this.expression)
return value
},
//数据变化时执行,从而执行回调cb
update() {
const value = parsePath(this.data, this.expression)
cb()
},
}
//获取watcher依赖数据
function parsePath(obj, expression) {
const segments = expression.split(".")
for(let key of segments) {
if(!obj) return
obj = obj[key]
}
return obj
}
watcher的依赖发生更新时,执行回调就是派发更新。
每个数据都只应该维护自己的数组,数组存放以来自己的watcher,优化defineReactive函数,通过闭包给每一个属性创建一个维护数组即dep
function defineReactive(obj, key, value) {
const dep = []
observer(value)
Object.defineProperty(obj, key, {
get() {
return value
},
set(newValue) {
if(newValue === value) return
observer(newValue)
value = newValue
dep.notify() //触发依赖
}
})
}
依赖收集:
页面在进行渲染时,渲染引擎解析模板,实例化Watcher,执行其内部get获取依赖的数据。但Object.defineProperty
重写getter方法,即重写数据访问行为。则在getter内部对watcher进行添加至dep中,即完成了依赖收集。
将Watcher设置到全局,如window上。读取数据时,触发getter,getter会获取到当前读取数据的Watcher,将这个Watcher收集到Dep中。
对Object.defineProperty的getter和Watcher的get进行修改:
// class Watcher
get() {
//依赖收集阶段的标志
window.target = this
const value = parsePath(this.data, this.expression)
window.target = null //求值完毕后重置,防止其他watcher触发
return value
}
// Object.defineProperty
get() {
dep.push(window.target) //封装为dep.depend()
return value
}
Dep实际上使用了发布订阅模式,当数据发生变化,会循环依赖列表,给所有的Wathcer通知一遍。
派发更新:
依赖变化时触发watcher回调,哪个watcher触发了getter读取了数据,则这个watcher依赖于这个数据,所以在这个数据的setter中进行回调操作的触发即派发更新。
set(newValue) {
if(newValue === value) return
value = newValue
observer(newValue)
dep.forEach(e => e.update()) //触发回调
}
如果父子组件进行嵌套时,父组件的watcher会被子组件覆盖,子组件渲染完毕target变为null,所以需要对window.target使用栈进行存储。
const targetStack = []
function pushTarget(_target) {
target.push(window.target)
window.target = _target
}
function popTarget() {
window.target = targetStack.pop()
}
//对watcher修改
get() {
pushTarget(this)
const value = parsePath(this.data, this.expression)
popTarget()
return value
}
Dep类:
class Dep {
constructor() {
this.subs = []
}
depend() {
//依赖收集
if(Dep.target) this.addSub(Dep.target)
}
//通知更新
notify() {
//通知watcher更新依赖即派发更新
const subs = [...this.subs]
subs.forEach(s => s.update()) //通知所有的watcher派发更新
}
//添加订阅
addSub(sub) {
this.subs.push(sub)
}
}
Watcher类:
class Watcher {
consturctor(data, expression, cb) {
this.data = data
this.expression = expression
this.cb = cb
}
get() {
pushTarget(this)
const value = parsePath(this.data, this.expression)
popTarget()
return value
}
update(oldValue) {
//修改后对旧值进行监听
this.value = parsePath(this.data, this.expression)
this.cb.call(this.data, this.value, oldValue)
}
}
**总结:**vue2响应式本质是observer进行观察转化变量为响应式,通过watcher类中介进行依赖获取,dep类进行更新。
class Observer {
constructor(value) {
this.value = value
this.walk()
}
walk() {
Object.keys(this.value).forEach(v => defineReactive(this.value, v))
}
}
class Dep {
constructor() {
this.subs = []
}
depend() {
if(Dep.target) {
this.addSubs(Dep.target)
}
}
notify() {
const subs = [...this.subs]
subs.forEach(s => s.update())
}
addSubs(target) {
this.subs.push(target)
}
}
Dep.target = null
const StackTarget = []
function pushTarget(_target) {
StackTarget.push(Dep.target)
Dep.target = _target
}
function popTarget() {
Dep.target = StackTarget.pop()
}
class Watcher {
constructor(obj, expression, callback) {
this.obj = obj
this.expression = expression
this.callback = callback
this.value = this.get()
}
get() {
pushTarget(this)
const value = parsePath(this.obj, this.expression)
popTarget()
return value
}
update() {
const oldValue = this.value
this.value = parsePath(this.obj, this.expression)
this.callback.call(this, this.value, oldValue)
}
}
function parsePath(obj, expression) {
const express = expression.split('.')
for(const exp of express) {
if(!obj) return
obj = obj[exp]
}
return obj
}
function observe(obj) {
if(typeof obj !== 'object') return
new Observer(obj)
}
function defineReactive(obj, key, value = obj[key]) {
let dep = new Dep()
observe(value)
Object.defineProperty(obj, key, {
get() {
dep.depend()
return value
},
set(newValue) {
if(value === newValue) return
value = newValue
observe(newValue)
dep.notify()
}
})
}
const obj = {data1: 'xxx', data2: 'xxx'}
observe(obj)
const _watcher = new Watcher(obj, "data1", (value, oldValue) => {
console.log(`新值为:${value}, 旧值为:${oldValue}`)
})
包括:发布者、订阅者、第三方
function fn1() {} //事件函数1
function fn2() {} //事件函数2
// 第三方eventBus、开启自定义事件监听
eventBus.on("eventName", fn1); //订阅者1
eventBus.on("eventName", fn2); //订阅者2
eventBus.$emit("eventName") //发布者$emit
//生命周期末,防止内存泄漏,删除自定义事件
eventBus.$off("eventName", fn1)
发布订阅模式与观察者模式相比,具有第三方这个媒介。
Vue3
响应式原理vue3
实现响应式使用的是proxy
代理(拦截对象属性的增删改),reflect
反射去处理。
new Proxy(data, {
get(target, prop) {
return Reflect.get(target, prop)
},
set(target, prop, value) {
// 视图更新
return Reflect.set(target, prop, value)
},
deleteProperty(target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy的优势:
函数声明:function add(a, b) {return a + b;}
表达式定义:var sum = function(a, b) {return a + b;}
Function构造函数:var sum = new Function(a, b) {return a + b;}
匿名函数和全局执行函数的this指向window
普通函数默认返回值为undefined,构造函数默认返回值为新创建的对象。
**盒子快速居中:**父盒子display(flex),子盒子margin:auto
padding和margin区别: 作用对象不同,padding作用于自身,margin作用于外部对象。
**vw
**和百分比区别:百分比有继承关系,vw
仅相对于视口宽度。
浏览器支持小字体:transform:scale
进行缩放
**深浅拷贝:**浅拷贝修改新数据会影响源数据(直接赋值、解构赋值:解构赋值一层相当于深拷贝、深层相当于浅拷贝)
深拷贝:JSON.parse(JSON.stringify(list))
转化函数时有问题。
深拷贝:(重要)
function deepClone(source) {
const target = source instanceof Array ? [] : {}
for(let key in source) {
if(source.hasOwnProperty(key)) {
if(source[key] && typeof source[key] === 'object') {
target[key] = source[key] instanceof Array ? [] : {}
target[key] = deepClone(source[key])
} else {
target[key] = source[key]
}
}
}
return target
}
元素隐藏:
加载:
减少http
请求(精灵图、文件合并)
资源压缩
CDN引入
SSR服务端渲染、预渲染
懒加载
减少DOM操作,避免reflow
。利用文档碎片createDocumentFragment
存放DOM统一处理。
利用自定义属性例如data-src
存放图片真实路径,src存放加载路径,在用户滑动至相应区域时加载图片发出请求。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<img src="预加载图片路径" data-src="真实路径" alt="image">
<img src="预加载图片路径" data-src="真实路径" alt="image">
<img src="预加载图片路径" data-src="真实路径" alt="image">
<script>
const imgs = document.querySelectorAll('img')
const len = imgs.length
let n = 0
window.onscroll = lazyload = () => {
for (let i = n; i < len; i++) {
if (imgs[i].offsetTop < document.documentElement.scrollTop + document.documentElement.clientHeight) {
if (imgs.getAttribute('src') === '预加载图片路径') {
imgs.getAttribute('src') = imgs.getAttribute('data-src')
}
n = i + 1 // 防止图片重复加载
}
}
}
script>
body>
html>
使用IntersectionObserver 进行懒加载
function lazyload() {
let observer = new IntersectionObserver((entires, observe) => {
entires.forEach(item => {
let target = item.target
if(item.isIntersecting && target.dataset.src) {
target.src = target.dataset.src
target.removeAttribute('data-src')
observe.unobserve() //取消观察
}
})
})
let imgs = document.querySelectorAll('img')
imgs.forEach(item => {
// 循环观察
observer.observe(item)
})
}
IntersectionObserver的优点:
默认绑定:一般this指向window,函数this指向其调用者,严格模式下指向undefined。
隐式绑定:对象内函数指向对象本身。
显式绑定:bind、call、apply传入对象,改变this的上下文指向为传入值对象。
new绑定:构造函数内的this指向创建出来的实例。
箭头函数没有this,其this为上一层this指向。(使用call、apply、bind改变this指向)
即变量和函数能被访问的区域。分为全局作用域、函数作用域、块级作用域。
为避免变量被污染、实现数据私有化,保存变量不被垃圾回收。(防抖节流、封装库)
闭包条件:嵌套函数、内部函数访问外部作用域的值、返回值为函数、创建对象函数使其长期驻留。(内部函数就是闭包!)
function fn() {
let a = 10;
function fn2() {
a--;
console.log(a);
}
return fn2;
}
const p = fn();
用于实例化一个对象。
const person = new Person();
// 实际流程
const obj = new Object(); // 创建空对象
obj.__proto__ = Person.prototype; // 空对象的原型指向其构造函数的原型对象
let result = Person.call(obj); // 空对象this作为构造函数的上下文
let person = typeof result === 'object' ? result : obj ; // 判断返回值类型(构造函数返回值为对象,普通函数返回值为undefined)
// 如果返回值类型为基本数据类型则返回值对象、若为引用数据类型则返回引用对象。
let obj1 = Object.create(null)
和 let obj2 = {}
区别:obj1
为纯净对象,没有原型链,而obj2
默认具有原型链指向Object。
事件执行分为事件捕获和事件冒泡阶段,通常情况下事件是在事件冒泡阶段执行的,
事件委托是利用事件冒泡,只设置一个事件,管理所有同一类型的事件。(利用e.target
)
将子元素的事件委托给父元素通过e.target
获取当前触发事件的元素。
优点:提高性能、减少事件的绑定、减少内存占用。
jquery的入口函数是匿名自执行函数
优点是自动执行、防止变量污染
(function(window){
})(window)
防抖:用户触发事件过于频繁,只保留最后一件事操作。(n秒后执行事件,若n秒内重复触发,则重新计时)
function debounce(fn, delay) {
let timer = null
return function() {
if(timer) clearTimeout(timer)
timer = setTimeout(()=> {
fn.call(this)
}, delay)
}
}
节流:控制高频事件的执行次数。执行完一次才能执行下一次。(n秒内只运行一次,n秒内多次触发,只有一次生效)
function throttle(fn, delay) {
let user = true
return function() {
if(user) {
setTimeout(()=> {
user = true
fn.call(this)
}, delay)
}
user = false
}
}
为了避免使用回调函数产生的“回调地狱”的ES6新异步解决方法。
其有三种状态:pending(待定),fulfilled(已兑现),rejected(已拒绝)
Promise内置resolve和reject函数。调用resolve为fufilled,调用reject为rejected。
具有all、race、resolve、reject等方法。
手写Promise:then方法可能有多种情况(返回Promise2、返回普通值、返回PromiseA+规范类型、返回其他规范类型)
class MyPromise {
constructor(executor) {
this.status = "pending"
this.value = undefined
this.reason = undefined
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = value => {
if (this.status === "pending") {
this.status = "fulfilled"
this.value = value
this.onFulfilledCallbacks.forEach(cb => cb(value))
}
}
const reject = reason => {
if (this.status === "pending") {
this.status = "rejected"
this.reason = reason
this.onRejectedCallbacks.forEach(cb => cb(reason))
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value
onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason}
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === "fulfilled") {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if (this.status === "rejected") {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if (this.status === "pending") {
this.onResolveCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
})
}
})
}
catch(onRejected) {
return this.then(null, onRejected)
}
}
function resolvePromise(promise2, x, resolve, reject) {
if(promise2 === x) {
return reject(new Error('死循环'))
}
let called = false // 锁
if(x instanceof MyPromise) {
x.then(value => {
resolvePromise(promise2, value, resolve, reject)
}, reason => {
if(!called) {
called = true
reject(reason)
}
})
} else if (x !== null && (typeof x === "object" || typeof x === "function")) {
try {
const then = x.then
if (typeof then === "function") {
then.call(x, value => {
if(!called) {
called = true
resolvePromise(promise2, value, resolve, reject)
}
}, reason => {
if(!called) {
called = true
reject(reason)
}
})
} else {
resolve(x)
}
} catch (error) {
if(!called) {
called = true
reject(error)
}
}
} else {
resolve(x)
}
}
接收一个由Promise对象组成的数组作为参数,返回一个Promise对象。当数组中所有Promise对象都成功执行才返回fulfilled状态,并返回一个由所有Promise对象返回值组成的数组。若其中任何一个对象发生错误则标志rejected状态,返回错误原因。
// 实现Promise.all
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
const results = []
let count = promises.length
if(count === 0) resolve(results)
for(let i = 0; i < count; i++) {
promises[i].then(value => {
results[i] = value
count--
if(count === 0) resolve(results)
}, reason => reject(reason))
}
})
}
ES7提出的用于提升Promise可读性的语法糖
async:用于声明函数为异步函数,且返回值为Promise
await:通常和async搭配使用,用于获取promise回调的值,且代码会等待不会继续向下执行,await之后代码变为微任务。
通常在多个promise执行时,不建议分别使用await,会使得一个回调结束后才去执行另一个回调。
async function fn() {
const promiseA = await fetch('xxx1')
const promiseB = await fetch('xxx2')
// 使用Promise.all解决,使其并行
const [promiseA, promiseB] = await Promise.all([fetch('xxx1'), fetch('xxx2')])
}
forEach不支持使用async和await。
由于JS单线程的机制,防止代码阻塞,将代码分为同步和异步。
同步代码:立即放入js引擎中执行,原地等待结果。
异步代码:先放入宿主环境(浏览器/Node)中,不必原地等待结果,不阻塞主线程继续执行,异步结果在将来执行。
同步代码先放入执行栈中,从前往后执行。异步代码先放宿主环境中,等待时机后放入任务队列中。执行栈执行完后去任务队列中检查,将任务队列中的异步任务放入执行栈中执行。执行栈执行完后还会回到任务队列中查找。反复循环过程就是事件循环。
异步任务可分为宏任务和微任务。
process.nextTick:在当前tick执行完毕后,下一个宏任务执行之前调用(可以看作最早执行的那个微任务)。
setImmediate:在事件循环的下一次迭代时触发。
**宏任务:**setTimeout、setInterval、DOM事件、AJAX请求、setImmdiate。
微任务:Promise、Async、Await、process.nextTick(Promise本身是同步的,但then和catch回调是异步的) 位于await之后的代码会被划入微任务!
微任务执行时机早于宏任务。(微任务>DOM渲染>宏任务)
script标签相当于最大的宏任务。先执行最大的宏任务,然后进入寻找同步->微任务->宏任务。
异步代码在事件循环中先放在宿主环境中,等待时机后放入任务队列中。而宏任务和微任务分别放在不同的队列中。
执行栈中代码执行完毕后会先从微任务队列中寻找异步任务,等待所有的微任务执行完毕后再去宏任务队列中寻找异步代码执行。
执行顺序:同步代码->process.nextTick->微任务->宏任务->setImmediate
都被称为vue中的类编译器。render函数将template模板转化为真实的DOM元素。
render函数中的h相当于原生JS中的createElement,用于创建真实元素。
&、<>、"、'
使用插件进行转义。原型链继承:子类通过原型链链接父类。
function Father() {
this.info = {
name: 'Admin',
age:18
}
}
Father.prototype.sayInfo = function() {
console.log(this.info)
}
function Child() {}
Child.prototype = new Father() // 原型链绑定
new Child().sayInfo() // 调用父类方法输出属性
借用构造函数继承:子类通过改变自身this执行借用父类构造函数。
优点:解决了原型链实现继承不能传参的问题。
缺点:借用构造函数方法都在父类中定义、无法实现复用。且父类定义在原型上的方法,子类无法访问。
function Father(name, age) {
this.name = name
this.age = age
}
function Son(name, age) {
Father.apply(this, [name, age])
}
console.log(new Son('Admin', 20)) // {name:'Admin', age: 20}
组合继承:原型链和借用构造函数组合在一起进行继承。
优点:解决了分别使用原型链继承和借用构造函数继承的问题。
缺点:无论什么情况下都会调用两次父类构造函数:创建子类原型和在子类构造函数内部。
function Father(name, age) {
this.name = name
this.age = age
}
Father.prototype.says = function() {
console.log(this.name, this.age)
}
function Son(name, age) {
Father.apply(this, [name, age])
}
Son.prototype = new Father()
Son.prototype.constructor = Son
new Son('Admin', 18).says() // 输出Admin,18
使用ES6的Class实现继承:子类extends继承父类并调用super方法(子类必须先调用super才具有自身的this,因为子类的this对象是继承于父类的this对象)
缺点:浏览器兼容问题
class Father {
constructor(name, age) {
this.name = name
this.age = age
}
says() {
console.log(this.name, this.age)
}
}
class Son extends Father {
constructor(name, age, gender) {
super(name, age)
this.gender = gender
}
}
new Son('Admin', 18, 'man').says()
下面三种方法需要使用到Object.create()
原型式继承
const a = {
name: 'Admin',
friend: [1,2,3],
say() {
console.log('Hello')
}
}
const b = Object.create(a)
b.name = 'test'
b.age = 30
寄生式继承
const parent = {
name: 'Admin',
friend: [1,2,3],
say() {
console.log('Hello')
}
}
function clone(source) {
const target = Object.create(source)
target.sayHi = function() {
console.log('Hi')
}
return target
}
原型式继承和寄生式继承缺点是由于为浅拷贝,存在篡改可能。
class Parent {
name: 'Admin'
}
Parent.prototype.say = function() {
console.log(this.name)
}
class Child {
Parent.call(this)
this.name = 'Admin2'
}
function clone(parent, child) {
// 使用Object.create减少组合继承中多一次构造的过程
child.prototype = Object.create(parent.prototype)
child.prototype.constructor = child
}
clone(Parent, Child)
Child.prototype._say = function() {
console.log(this.name)
}
let child = new Child()
在不重新加载整个网页的前提下,与服务器交换数据并更新部分内容。
通过XMLHttpRequest向服务器发送异步请求,拿到数据后同通过JS更新。
过程:
创建XMLHttpRequest对象,xhr。
通过xhr中的open方法和服务器建立连接。open(请求方法,URL,是否异步)
构建请求所需的数据,通过xhr的send方法发送给服务器。
通过xhr的onreadystatechange事件监听服务器和客户端的通信状态。
接受处理服务器的数据并更新页面。
const xhr = new XMLHttpRequest()
xhr.open('get', 'url', true)
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
//成功
console.log(xhr.response);
}
}
}
xhr.send(null)
promise封装Ajax:
function promiseAjax() {
let promise = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('get', 'url', true)
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
resolve(xhr.response)
} else {
reject(new Error(xhr.statusText))
}
}
}
xhr.send(null)
})
return promise
}
promiseAjax.then(result => {}).catch(err => throw err)
get和post的区别:
渲染引擎通过网络获得请求文档的内容。
解析HTML文件,构建DOM Tree。
解析CSS,构建CSSOM Tree。
将 DOM Tree 和 CSSOM Tree合并,构建Render tree(渲染树)
进行重排:根据Render Tree进行节点信息计算。
进行重绘:根据计算好的信息绘制页面。
JS的加载可能会引起阻塞页面渲染,在尚未构建好CSSOM时JS不能执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aFY24F1x-1684887492811)(C:\Users\LWH\AppData\Roaming\Typora\typora-user-images\image-20230412095107622.png)]
精灵图:将多张小图整合在一张大图上,利用background-position进行显示。减少图片请求、提高加载速度。
Base64:将图片以二进制编码形式使用,base64会和html、css一起下载到浏览器中可以减少请求和跨域问题。但体积如果大于原图片大小,则不利于css加载。
svg:基于XML的图像格式,体积小且不会失真,本质是文本文件。可以被DOM和js操作。
JSON Web Token:通过JSON形式作为在web中的令牌,可以在各方之间安全的把信息作为JSON对象传输、授权等。
认证流程:
优点:简洁、Token是以JSON加密的形式保存在客户端,原则上任何web形式都支持。
在响应拦截器中拦截,判断token过期后,调用刷新token接口。
使用双Token进行无感刷新,access_token和refresh_token。
登录成功保存access_token和refresh_token。
当access_token过期时,自动发送refresh_token到刷新token的请求中刷新。
得到新token后,重新发送刚刚未完成的请求,实现无感刷新。
若refresh_token过期,则清除所有token,返回登陆页面。
(实现无感,用户请求必须保留。得到新token后必须重新请求。当同时出现多个请求时,可能会导致请求刷新多次token,需要设置标志判断是否正在请求刷新token且设置队列存储请求方法。)
-webkit-user-select:none
overflow:scroll/auto
在IOS下滑动卡顿使用·-webkit-overflow-scrolling:touch
v-if和v-show区别:
都可以控制元素的显示和隐藏
MVVM:
v-for中key值作用:
在created和mounted中请求数据区别:
vue中的修饰符:
elementUI表单验证:
keep-alive:
watch和computed的区别:
父传子:
props:['属性名']
vue3:const props = defineProps({属性名:类型})
子传父:
vue3:
const emit = defineEmits([‘事件名’])`兄弟传:利用VueComponent.prototype.__proto__ === Vue.prototype
全局事件总线:$bus
// main.
js
const app = new Vue({
el: '#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this
}
})
子组件绑定事件和触发时机: o n 和 on和 on和emit
l i s t e n e r 和 listener和 listener和attrs组件透传
provide和inject
祖先组件中定义provide属性,返回传递的值。
后代组件通过inject接收组件传递过来的值。
//祖先组件
provide() {
return {foo: 'foo'}
}
//后代组件
inject: ['foo']
vuex
import axios from 'axios'
const $axios = axios.create({
baseURL: '',
timeout: 3000
})
//或者设置 axios.defaults.baseURL/timeout 含义相同
$axios.interceptors.request.use(config => {
return config
}, err => {
Promise.reject(err)
})
$axios.interceptors.response.use(res => {
return Promise.resolve(res)
}, err => {
Promise.reject(err)
})
export default $axios
Vue路由传参:
this.$router.push({name: 'Admin', params: {id: 1}})
this.$route.params.id
获取值this.$router.push({name:'/index/${item.id'}})
{path: '/index/id'}
获取this.$router.push({name: 'index', query:{id: item.id}})
Hash模式和History模式区别:
connect-history-api-fallback
中间件。路由拦截:
通过vue-router的beforeEach方法进行每一次路由的拦截,判断拦截信息中是否有鉴权邀请或权限校验,实现鉴权。在路由的meta源信息中配置。
const routes = [
{
name: 'index',
path: '/index',
component: Index,
meta: {
requirtAuth: true
}
}
]
router.beforeEach((to, from, next) => {
if(to.meta.requirtAuth) {
if(store.state.token) {
next()
} else {}
}
})
动态路由:
解决刷新后二次加载路由:
window.location.reload() 可能会造成闪屏
matcher:
const router = createRouter()
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher
}
分为state、getters、mutations、actions、modules。
vuex刷新丢失数据处理:
vue中可以直接触发methods中的方法,vuex不行。vuex通过dispatch来访问actions中的方法,actions中的commit触发mutations中的方法,从而修改state的值,最后通过getter更新视图。
Vue.use(vuex),调用install方法,通过applyMixin(vue)在任意组件内执行this.$store就可以访问到store对象。
location.reload()
this.$router.go(0)
provide和inject:
//子组件中调用
inject/provide刷新页面相对于其他刷新方法,不会使页面白屏,增强用户交互感。
路由懒加载
非首屏的组件使用异步组件
首屏不重要的组件延迟加载
静态资源放在CDN上
减少首屏JS、CSS等资源文件的大小
使用服务端渲染
尽量减少DOM的数量和层级
使用精灵图
开启GZIP压缩
图片懒加载
基于TCP的H5新协议、实现了浏览器和服务器全双工通信,是持久化协议,位于应用层。
使用场景:通常应用高即时性服务,例如聊天室、游戏、新闻直播实时更新。
特点:
与http协议具有良好的兼容性。
建立在TCP协议之上。
数据格式轻量、与http协议都属于应用层。
可以发送文本,也可以发送二进制。
没有同源限制、能与任意服务器通信。
websocket和http:
child_process
模块来并行运行多个进程,或者使用Worker
来运行多个线程。指传递给一个组件数据或方法,但该组件没有使用props或emits显式声明。例如class、style、id
组件透传为子组件提供了不需要显式声明属性或方法的条件,即只需要声明其默认参数,对其他参数或方法使用v-bind:$attrs
和v-on:$listeners
传入,并在子组件使用inheritAttrs:false声明(即禁止属性继承)。
在vue3中若使用setup语法糖,可以通过**useAttrs()**访问透传的属性。
若不使用setup语法糖,则在setup函数内部接收attrs参数**(ctx上下文包含了attrs、slots、emit、expose等属性)**
export default {
setup(props, ctx) {
console.log(ctx.attrs) //即透传的属性
}
}
.native即在组件的根元素上监听一个原生事件。
.sync即对一个prop进行双向绑定。
传统上prop不允许在子组件中修改,因为子组件修改父组件会造成无法找到变更来源。一般通过父组件传函数给子组件,子组件通过操作传入函数来改变父组件的值。
使用sync简化了父组件传函数子组件修改父组件值的写法。
子组件执行事件必须是update:value
格式
函数柯里化是一种将一个接受多个参数的函数变换成一系列只接受一个参数的函数的技术,用于提高代码的可读性、复用性,以及延迟计算。
柯里化的核心即函数内返回函数。即核心为递归和闭包。
eg:设计一个函数,使其满足 add(1)(2)(3)(4)//输出10、add(1,2)(3)(4)//也输出10、add(1)(2,3)(4)//也输出10
function add() {
let args = Array.prototype.slice.call(arguments)
let inner = function() {
args.push(...arguments)
return inner
}
inner.toString = function() {
return args.reduce((prev, curr) => {
return prev + curr
})
}
return inner
}
console.log(add(1)(2)(3).toString())
Bus.js
class Bus {
constructor() {
this.callbacks = {}
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || []
this.callbacks[name].push(fn)
}
$emit(name, ...args) {
if(this.callbacks[name]) {
this.callbacks[name].forEach(cb => cb(...args))
}
}
}
// 直接在vue原型上挂载
Vue.prototype.$bus = new Bus()
Children1.vue
this.$bus.$emit('foo', data)
Children2.vue
this.$bus.$on('foo', data => {})
== 会发生强制类型转化再进行值的比较,=== 不会转化直接进行比较。
两个都为简单类型,字符串和布尔值都会转换成数值,再比较
简单类型与引用类型比较,对象转化成其原始类型的值,再比较
两个都为引用类型,则比较它们是否指向同一个对象
null 和 undefined 相等
存在 NaN 则返回 false
原始事件模型:DOM0事件
document.getElementById('test').onclick = function() {}
标准事件模型:DOM2事件
function ajax(options) {
let xhr = new XMLHttpRequest()
options = options || {}
options.data = options.data || {}
options.dataType = options.dataType || 'json'
options.type = (options.type || 'GET').toUpperCase()
if(options.type === 'GET') {
xhr.open(options.type, `${options.url}?${options.data}`, true)
xhr.send(null)
} else if(options.type === 'POST') {
xhr.open(options.type, options.url, true)
xhr.send(options.data)
}
xhr.onreadystatechange = function() {
if(xhr.readystate === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
options.success && options.success(xhr.responseText, xhr.responseXml)
} else {
options.fail && options.fail(xhr.status)
}
}
}
}
Function.prototype.myBind = function(...args) {
const fn = this, self = args[0]
args.shift()
return function() {
fn.apply(self, args)
}
}
reduce:对数组的每一项按序执行给定的函数,每一次运行会将之前元素的计算结果作为参数传入,最后将结果汇总为单个值。
map:创建一个新数组,由原数组的每个元素经过提供函数执行后的返回值组成。
即在函数尾部调用自身,是递归的特殊情形。
特点:
优化示例:
数组求和:
function countSum(arr, total = 0) {
if(arr.length === 0) return total
return countSum(arr, total + arr.pop())
}
斐波那契数列:
function factorial(n, start, total) {
if(n < 3) return total
return factorial(n - 1, total, start + total)
}
核心:
图片等静态资源在使用之前要提前请求。
资源后续使用时要从缓存中加载,提升用户体验。
页码要全部资源加载完再展示,防止白屏。
实现:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
head>
<body>
<img src="http://up.deskcity.org/pic_source/2f/f4/42/2ff442798331f6cc6005098766304e39.jpg" alt="image">
<script>
const imageSrcs = [ "https://img1.baidu.com/it/u=2812417321,4100104782&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750",
"https://img1.baidu.com/it/u=1960110688,1786190632&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=281",
"https://img2.baidu.com/it/u=1361506290,4036378790&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500"]
const img = document.querySelector('img')
let flag = 0
preLoad(imageSrcs[0])
img.addEventListener('click', () => {
if (flag < imageSrcs.length) {
img.src = imageSrcs[flag]
flag++
if (flag < imageSrcs.length) {
preLoad(imageSrcs[flag])
}
} else {
console.log('已经达到最后一张')
}
})
function preLoad(src) {
return new Promise((resolve, reject) => {
const image = new Image()
image.addEventListener('load', () => resolve(src))
image.addEventListener('error', () => reject())
image.src = src
})
}
script>
body>
html>
通过canvas的相关API实现。
function compressPic(img) {
img.addEventListener('load', () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
const data = canvas.toDataURL('image/jpeg', 0.7)
img.src = data
return data
})
}
单行省略: white-space强制换行,overflow隐藏超出部分,text-overflow使用省略号隐藏超出部分。
p {
width: 200px
height: 40px;
line-height: 40px;
overflow: hidden;
text-overflow: ellipse;
white-space: norwap;
}
多行省略: display设置对象为弹性伸缩盒子模型,-webkit-line-clamp限制块元素中显示的文字行数,-webkit-box-orient设置弹性伸缩盒子子元素排列方式。
p {
width: 200px;
height: 40px;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
white-space: ellipse;
}
loader用于对模块的“源代码”进行转化,在import或加载模块时与处理文件。
webpack内部,任何文件都是模块,默认情况下遇到import或require加载模块时,webpack只支持对js和json文件打包,css、png等类型文件就需要配置对应loader去解析。
配置方式:
在webpack.config.js
中的module.rules
属性内配置。
rules为数组,可配置多个loader,每个loader是一个对象,test为正则匹配文件规则,use为数组,内容为loader的值。
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: ["css-loader", "style-loader"]
}
]
}
}
loader特性: loader支持异步,其运行在Nodejs中。
常用loader:
plugin赋予模块各种灵活的功能,如打包优化、资源管理、环境变量注入,贯穿了webpack的整个编译周期。
plugin目的在于解决loader无法实现的功能。
配置方式:
在webpack.config.js
的plugins
中配置。
plugins为数组,其内部为各种plugin的new实例。
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
plugins: [
new HtmlWebpackPlugin({template: './src/index.html'})
]
}
常用plugin:
即webpack提供的代理服务,将客户端发送的请求转发给其他服务器,解决跨域问题。
使用webpack-dev-server
,即webpack的代理服务器,在webpack.config.js
中配置devServer
属性实现代理。
module.exports = {
devServer: {
proxy: {
'./api': {
target: '代理地址',
}
}
}
}
devServer
中的proxy
配置代理,地址:{ 配置项 }
JS代码压缩:使用terser
压缩代码,减小bandle的大小。
通过optiomization.minimizer
属性进行配置
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true // CPU核数-1
})
]
}
}
Tree-shaking: 消除死代码
实现方案:
usedExports:标记函数是否被使用,通过Terser优化。
配置userExports为true
module.exports = {
optimization: {
usedExports: true
}
}
sideEffects:跳过整个模块/文件。直接查看是否有副作用。
在package.json
中设置sideEffect
属性,告知webpack删除未用到的exports属性。
// 配置需要保留的文件
"sideEffect":[
"./src/util/format.js",
"*.css"
]
使用热更新,无需重新刷新整个页面即可更新代码。
通过配置devServer中的hot属性即可开启热更新。
const webpack = require("webpack")
module.exports = {
devServer: {
hot: true
}
}
原理:
webpack-dev-server
创建两个服务器:提供静态资源服务和Socket服务。HTTP即超文本传输协议,是网络通信的一种规范,常用于web浏览器和网站服务器间传输信息,以明文方式发送内容,不提供加密。
HTTPS即将HTTP运行安全的SSL/TLS
协议上,即HTTPS = HTTP+SSL/TLS,通过SSL证书验证服务器身份,加密服务器和浏览器之间的通信。
区别:
UDP: 用户数据报协议,面向数据报的通信协议,对应用层下发的报文不处理,加入首部下发给网络层。
特点:
TCP: 传输控制协议,是可靠的面向字节流的通信协议,将应用层下发的数据看作无结构的字节流发送。
特点:
TCP是面向有链接的协议,只有在确实通信对端后才会发生数据。
TCP可以进行丢包时的重发控制,还可以对乱序后的数据进行纠正。
**区别:**两者都是处于运输层的协议。
TCP适用于对效率要求低、准确性高、有链接的场景。UDP适用于对效率要求高、准确性要求低的场景。
主要指封装http请求的各种处理方法。
使用Express封装中间件:
定义中间件:参数(req请求对象、res响应对象、next函数)
const logger = (req, res, next) => {
console.log(req.method, req.url)
next()
}
express调用:
const app = require('express')()
app.use(logger)
app.get('/', (req, res) => {
res.send('hello world')
})
process: nodejs内置的全局变量,提供了进程信息与控制方法。
fs: 文件模块,提供了对本地文件读写的能力。
Buffer: 开辟空间存放二进制数据,本质即一个数组存储二进制数据。
Buffer.from
const b1 = Buffer.from('10')
const b2 = Buffer.from('10', 'utf-8')
const b3 = Buffer.from([10])
const b4 = Buffer.from(b3)
console.log(b1, b2, b3, b4);
//
Buffer.alloc
const bAlloc1 = Buffer.alloc(10)
const bAlloc2 = Buffer.alloc(10, 1)
console.log(bAlloc1); //
console.log(bAlloc2); //
EventEmitter: Node采用事件驱动机制,EventEmitter是事件驱动的基础。
git reset: 用于回退版本,遗弃不再使用的提交。
git reset
git reset <ID> //按照ID删除版本
git revert: 在当前提交后再新增一次提交,抵消掉上一次提交导致的变化,不会改变过去的历史,用于安全的取消过去发布的提交。
git revert <commit_id>
git revert HEAD //撤销前一次的提交
git revert HEAD^ //撤销前前一次的提交
区别:
即单页面应用程序,通过动态重写当前页面来与用户进行交互,避免了页面之间切换打断用户体验。
优点:
缺点:
SPA首屏加载慢解决方法:
减少入口文件体积:使用路由懒加载
routes: [
path: 'Blogs',
name: 'Blogs',
component: () => import('./component/Blogs.vue')
]
静态资源本地缓存
UI框架按需加载
图片压缩:图片资源压缩(base64)、页面icon采用在线版本,雪碧图等。
开启GZIP压缩:安装compression-webpack-plugin
使用SSR,采用nuxt.js。
由于Vue2采用的是Object.defineProperty实现的响应式,而其内部的getter和setter无法劫持到对象的属性添加和删除,因为对象的属性添加和删除没有经过Object.defineProperty设置为响应式。
Vue.set: 实质是调用defineReactive向对象内部添加属性使其成为响应式。
Object.assign: 创建一个新对象,合并源对象和混入对象的属性,
$forceUpdate: 迫使Vue实例进行重新渲染
如果修改的是数组页面未变化,则可能是未使用vue变异的数组方法(pop、push、unshift、shift、sort、reverse、splice)
vue2在mounted中修改数据也不会发生变化,因为vue2数据更新和视图更新是异步的,会等待下一次事件循环才进行渲染。可以使用nextTick解决。
allowReactivity
助推式是响应式系统,允许在同一事件循环中处理依赖关系,并立即更新视图。v-for
的优先级高于v-if
导致性能上会有浪费。每次渲染都会先循环再判断。
避免方案:
外部套一层template,在此进行v-if判断。内部进行v-for循环。
<template v-if="isShow">
<p v-for="item in items">{{item}}p>
template>
通过计算属性提前过滤掉不需要展示的数据。
computed: {
items: function() {
return this.items.filter(item => item.isShow)
}
}
父子通信:
父通过props传递给子数据。
//父传子
//子接收
子通过$emit传递给父数据。
//父接收
//子改变
this.emit('add', data)
父通过$ref获取子元素实例和其身上的数据。
this.$ref.children.data //获取子组件实例的数据
兄弟通信:
事件总线$eventBus
//入口函数绑定$bus
new Vue({
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this
}
}).$mount('#app')
//自定义eventBus
class Bus {
constructor() {
this.callbacks = {}
}
$on(eventName, fn) {
this.callbacks[eventName] = this.callbacks[eventName] || []
this.callbacks[eventName].push(fn)
},
$emit(eventName, ...args) {
if(this.callbacks[eventName]) {
this.callbacks[eventName].forEach(cb => cb(args))
}
}
}
// 接收方
this.$on('event', data => {
this.xxx = data
})
// 发送方
this.$emit('event', this.args)
祖孙与后代通信:
组件透传 l i s t e n e r s 和 listeners和 listeners和attrs:即未被props和emit显式声明的属性,存在于上下文对象context中
//子使用
{{$attrs.foo}}
//隔代传
//children接收展开
//grandson接收
{{ foo }}
provide和inject
// 祖先
provide() {
return {
name: value
}
}
// 后代
inject: ['name']
// 祖先
import { provide } from 'vue'
provide('name', 'value')
// 后代
import { inject } from 'vue'
const data = inject('name')
非关系组件通信:
Vue中DOM更新是异步的,数据发生变化时,Vue将开启一个异步更新队列,等队列中所有数据变化完成后再进行统一更新。
nextTick用于在下一次DOM更新结束后执行延迟回调,在数据修改后执行方法,获取更新后的DOM。
原理:
分发Vue组件中可复用的功能,本质是一个js对象,包含了组件中的任意功能项。
局部混入:
const myMixin = {
created() {
this.hello()
},
methods: {
hello() {
console.log('hw')
}
}
}
//组件通过mixins调用mixin对象
Vue.component('componentA', {
mixin: [myMixin]
})
全局混入: 通过Vue.minxin()
进行全局的混入
Vue.mixin({
created() {
console.log('全局混入')
}
})
全局混入会影响到每一个组件实例,常用于插件的编写。
拓展组件,复用组件对其进行定制化处理的方式,即向子组件特定位置插入HTML代码。
默认插槽:子组件
确定渲染位置,父组件直接在子组件标签内部写入内容。
默认显示内容
传入内容
具名插槽:子组件设置name
属性来表示插槽的名字,不传则为默认插槽。
默认内容
内容
Vue3中,v-slot:name可以简写为template #name,即:
content
作用域插槽:子组件向父组件传递数据,在slot上进行挂载,父组件通过v-slot:组件名="数据名"
获取。
{{slotProps.username}} {{slotProps.age}}
key是给每一个VNode唯一的ID,是diff算法的优化策略,可以根据key,更准确更快的找到对应的Vnode节点。
Keep-alive用于缓存组件,在组件切换后仍保留其状态,防止反复渲染DOM,避免频繁的销毁和重建组件。
配置 include 和 exclude 进行名称匹配,max设置最大缓存实例数。
keep-alive缓存的组件,自带两个新的生命周期:activated 与 deactivated
原理:
通过自定义指令扩展Vue,实现一些自定义行为。
使用 Vue.directive() 进行注册,参数分别为指令名和配置对象。
Vue.directive('my-directive', {
// el:指令绑定元素、binding:指令对象、vnode:vue编译生成的虚拟节点、oldVode:上一个虚拟节点
bind(el, binding, vnode) {
//绑定指令的逻辑
},
inserted(el) {
//被指令绑定元素插入父节点时调用
},
update(el, binding, vnode, oldVnode) {
//组件更新的逻辑
},
unbind(el, binding, vnode) {
//解绑指令的逻辑
}
})
除了el、其他属性都是只读属性。
v-model是实现双向数据绑定的一个语法糖,底层依靠的是组件的props和监听事件。在表单中,实际上是:value和@input的组合实现。
实现 v-model 指令的关键是监听 input 事件,并通过 $emit 发送一个名为 input 的自定义事件,并携带 input 元素的值。
父组件中使用v-model指令来绑定数据和事件。
{{ message }}
diff算法是通过同层的树节点进行比较的高效算法。
特点:
步骤:
vue3的diff:
对比:
axios是轻量的HTTP客户端,基于XMLHttpRequest服务来执行HTTP请求。
封装Axios:
设置接口请求前缀:
if(process.env.NODE_ENV === 'development') {
axios.defaults.baseURL = 'http://localhost:5127/api'
} else if(process.env.NODE_ENV === 'production') {
axios.defaults.baseURL = 'http://43.1.3.123/api'
}
设置请求头和超时时间:
const request = axios.create({
timeout: 30000,
headers: {
get: {
'Content-Type' : 'application/x-www-form-urlencoded;charset=utf-8'
},
post: {
'Content-Type' : 'application/json;charset=utf-8'
}
}
})
封装请求方法:
export function httpGet({
url,
params = {}
}) {
return new Promise((resolve, reject) => {
axios.get(url, {params}).then(res => resolve(res.data)).catch(err => reject(err))
})
}
请求拦截器:
axios.interceptors.request.use(config => {
token && (config.headers.Authorization = token)
return config
})
响应拦截器:
axios.interceptors.response.use(response => {
if(response.status === 200) {
if(response.data.code === 401) {
} else if(response.data.code === 403) {
} else {
return Promise.reject(response)
}
} else {
return Promise.reject(response)
}
}, err => {
//对异常状态统一处理
if(error.response.status) {
return Promise.reject(error.response)
}
})
取消请求: 使用CancelToken的API
const CancelToken = axios.CancelToken
const source = CancelToken.source()
axios.get('xxx', {
cancelToken: source.token
}).then(res => console.log(res))
.catch(err => {
if(axios.isCancel(err)) {
console.log('请求取消', err.message)
} else {
console.log(err)
}
})
source.cancel('手动取消请求')
实现Axios:
class Axios {
constructor() {}
get(url, config) {
return this.request({...config, url, method: 'get'})
}
post(url, data, config) {
return this.request({..config, url, method: 'post', data})
}
request(config) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
const {url = '', data = {}, method = 'get'} = config
xhr.open(url, method, true)
xhr.onload = function() {
if(this.response.status >= 200 && this.response.status < 300) {
return resolve({data: this.response})
} else {
return reject(new Error(this.statusText))
}
}
xhr.send(data)
xhr.onerror = function() {
return reject(new Error('Network Error'))
}
})
}
}
在vue.config.js中配置devServer实现跨域处理。
module.exports = {
devServer: {
host: '127.0.0.1',
port: 8080
proxy: {
'/api': {
target: '目标地址'
changeOrigin: true,
pathRewrite: {
'^/api': ""
}
}
}
}
}
Object.defineProperty
内部get、set会劫持对属性的访问和修改,vue通过方法定义设置数据为响应式。
但不能检测到数组的修改、vue通过重写原生数组的七个方法使其变异修改变为响应式。
同样也无法检测到对象属性的新增和删除,vue通过 s e t 和 set和 set和delete语法糖检测。
如果对象是深层则不断需要遍历监听,造成性能问题。
function observe(obj) {
if(typeof obj !== "object" || obj == null) {
return obj
}
Object.keys(obj).forEach(k => {
defineReactive(obj, key)
})
}
function defineReactive(obj, key, value = obj[key]) {
observe(value)
Object.defineProperty(obj, key, {
get() {
return value
},
set(newValue) {
if(value !== newvalue) {
value = newValue
observe(newValue)
}
}
})
}
Proxy
通过对整个对象代理实现响应式,配合Reflect的相关API实现。
function reactive(obj) {
if(!isObject(obj)) return obj
return new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
return isObject(res) ? reactive(res) : res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
return isObject(res) ? reactive(res) : res
},
deleteProperty(target, prop) {
const res = Reflect.deleteProperty(target, prop)
return isObject(res) ? reactive(res) : res
}
})
}
function isObject(obj) {
return typeof obj !== "object" && obj != null ? false : true
}
区别:
Object.definePeroperty
只能通过遍历对象属性进行劫持,Proxy
是直接劫持整个对象并返回一个新对象。Proxy
可以直接监听到数组的变化(pop、push、splice等)SSR指服务端渲染,服务侧完成页面的HTML结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态和事件。
优点:
服务端渲染解决了SEO的问题,搜索引擎优先爬取HTML结构,使用ssr服务端生成了与业务相关联的HTML,有利于SEO。
首页呈现渲染,用户无需等待页面所有js加载完成就可以看到页面的视图。
缺点:
用户只需要登陆一次就可以访问所有互相信任的应用系统。当一个系统成功登陆后,
passport
会发放令牌给各个子系统,子系统拿令牌获取各自资源,一定时间内无需再次向passport
发起认证。
同域名:
不同域名:
将SessionID或Token保存到浏览器的LocalStorage中,通过iframe+postMessage将其写到多个域下的LocalStorage中。
const token = result.data.token
const iframe = document.createElement('iframe')
iframe.src = "http://xxx.com/index.html"
document.body.append(iframe)
setTimeout(() => {
iframe.contentWindow.postMessage(token, "http://xxx.com")
}, 4000)
setTimeout(() => {
iframe.remove()
}, 6000)
window.addEventListener('message', function(event) {
localStorage.setItem('token', event.data)
})
flter筛选
let source = [1,2,2,3,3]
let target = source.filter((ele, index) => {
return source.indexOf(ele) === index
})
set去重
let source = [1,2,2,3,3]
let target = Array.from(new Set(source))
循环判断
let source = [1,2,2,3,3]
let target = []
source.forEach((ele, index) => {
if(source.indexOf(ele) === index) target.push(ele)
})
利用reduce
let source = [1,2,2,3,3]
let target = source.reduce((pre, cur) => {
return pre.includes(cur) ? pre : [...pre, cur]
}, [])
利用对象key值唯一性
let source = [1,2,2,3,3]
let target = []
source.forEach(e => {
target[e] = ''
})
target = Object.keys(target).map(e => parseInt(e))
双循环判断
let source = [1,2,2,3,3]
let target = []
let flag
for(let i = 0; i < source.length; i++) {
flag = false
for(let j = i + 1; j < source.length; j++) {
if(source[i] === source[j]) flag = true
}
if(!flag) target.push(source[i])
}
用URL定位资源、用HTTP动词(GET、POST、PUT、DELETE)描述操作、用HTTP STATUS/CODE定义操作结果。
由于浮动元素会脱离文档流,故浮动的父元素高度会塌陷,导致页面紊乱。故需要通过操作来清除浮动,使父元素重新获得高度。
空元素清除
.clearfix::after {
content: "",
display: block;
clear: both;
}
父元素添加浮动
.parent {
float: left;
}
父元素设置overflow:hidden
.parent {
overflow: hidden;
}
使用伪类:before清除浮动
.clearfix::before,
.clearfix::after {
content: "";
display: block;
}
.clearfix::after {
clear: both;
}
.clearfix {
*zoom: 1;
// IE6/7清除浮动需要设置的属性
}