Doctype是HTML5的文档声明,通过它可以告诉浏览器,使用哪一个HTML版本标准解析文档。在浏览器发展的过程中,HTML出现过很多版本,不同的版本之间格式书写上略有差异。如果没有事先告诉浏览器,那么浏览器就不知道文档解析标准是什么?此时,大部分浏览器将开启最大兼容模式来解析网页,我们一般称为怪异模式,这不仅会降低解析效率,而且会在解析过程中产生一些难以预知的bug,所以文档声明是必须的。
HTML标签的语义化,简单来说,就是用正确的标签做正确的事情,给某块内容用上一个最恰当最合适的标签,使页面有良好的结构,页面元素有含义,无论是谁都能看懂这块内容是什么。语义化的优点如下:在没有CSS样式情况下也能够让页面呈现出清晰的结构,有利于SEO和搜索引擎建立良好的沟通,有助于爬虫抓取更多的有效信息,爬虫是依赖于标签来确定上下文和各个关键字的权重,方便团队开发和维护,语义化更具可读性,遵循W3C标准的团队都遵循这个标准,可以减少差异化。
结构层类似于盖房子需要打地基以及房子的悬梁框架,它是由HTML超文本标记语言来创建的,也就是页面中的各种标签,在结构层中保存了用户可以看到的所有内容,比如说:一段文字、一张图片、一段视频等等。
表示层是由CSS负责创建,它的作用是如何显示有关内容,学名:层叠样式表,也就相当于装修房子,看你要什么风格的,田园的、中式的、地中海的,总之CSS都能办妥。
行为层表示网页内容跟用户之间产生交互性,简单来说就是用户操作了网页,网页给用户一个反馈,这是JavaScript和DOM主宰的领域。
严格模式:是以浏览器支持的最高标准运行
混杂模式:页面以宽松向下兼容的方式显示,模拟老式浏览器的行为
常见的块级元素:p、div、form、ul、li、ol、table、h1、h2、h3、h4、h5、h6、dl、dt、dd。
常见的行级元素:span、a、img、button、input、select。
使用行内元素需要注意的是:
行内元素设置宽度width无效,行内元素设置height无效,但是可以通过line-height来设置,设置margin只有左右有效,上下无效,设置padding只有左右有效,上下无效。
可以通过display属性对行内元素和块级元素进行切换(主要看第 2、3、4三个值)
HTML5主要是关于图像、位置、存储、多任务等功能的增加:
语义化标签,语义化结构:article、aside、footer、header、nav、section、details、dialog、figure、mark、meter、progress等,多媒体video、audio、embed、source、track,画布canvas,表单控件datalist、keygen、output,calemdar、date、time、email,地理,本地离线存储,localStorage长期存储数据,浏览器关闭后数据不丢失,sessionStorage的数据在浏览器关闭后自动删除,拖拽释放。
由于它们的以上区别,所以它们的应用场景也不同,Cookie一般用于存储登录验证信息SessionID或者token,LocalStorage常用于存储不易变动的数据,减轻服务器的压力,SessionStorage可以用来检测用户是否是刷新进入页面,如音乐播放器恢复播放进度条的功能。
cookie 和 session 区别:数据存储位置、安全性、服务器性能消耗、存储大小和个数。
共同点:cookie 和 session 都是普遍用来跟踪浏览用户身份的会话方式。
不同点:cookie 数据存放在客户端,session 数据放在服务器端。
cookie 本身并不安全,考虑到安全应当使用 session。session 会在一定时间内保存在服务器上。如果访问量比较大,会比较消耗服务器的性能。考虑到减轻服务器性能方面的开销,应当使用 cookie 。单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个域名最多保存 50 个 cookie。 将登陆信息等重要信息存放为 session、其他信息如果需要保留,可以放在 cookie 中。
css盒子模型又称为框模型(Box Model),包含了元素内容(content)、内边距(padding)、边框(border)、外边距(margin)几个要素。所有HTML元素可以看作盒子,在CSS中,"box model"这一术语是用来设计和布局时使用。盒模型允许我们在其它元素和周围元素边框之间的空间放置元素。
不同部分的说明:
图中的内层是content依次是padding border margin。通常我们设置背景时就是内容、内边距、边框这三部分,如果border设置颜色的时候会显示boder颜色当boder颜色是透明时会显示background-color的颜色。而该元素的子元素的是从content开始的。而外边距是透明的,不会遮挡其他元素。
元素框的总宽度=width+padding-left+padding-right+border-left+border-right+margin-left+margin-right;
元素框的总高度=height+padding-top+padding-bottom+border-top+border-bottom+margin-top+margin-bottom;
IE盒模型又叫怪异盒模型,图中的内层是content依次是content padding border。通常我们设置背景时就是内容、内边距、边框这三部分。而外边距是透明的,不会遮挡其他元素。
元素框的总宽度=width(padding-left+padding-right+border-left+border-right);
元素框的总高度=height(padding-top+padding-bottom+border-top+border-bottom);
两个模型宽度和高度的计算不同
w3c中的盒子模型的宽:包括margin+border+padding+width; width:margin2+border2+padding2+width; height:margin2+border2+padding2+height;
iE中的盒子模型的width:包括border+padding+width;
上面的两个宽度相加的属性是一样的。 因此我们应该选择标准盒子模型,在网页的顶部加上 DOCTYPE 声明。
选择器优先级由高到低分别为: !important > 作为style属性写在元素标签上的内联样式 >id选择器>类选择器>伪类选择器>属性选择器>标签选择器> 通配符选择器(* 应少用)>浏览器自定义;
当比较多个相同级别的CSS选择器优先级时,它们定义的位置将决定一切。下面从位置上将CSS优先级由高到低分为六级:
css继承特性主要是指文本方面的继承(比如字体、颜色、字体大小等),盒模型相关的属性基本没有继承特性。
不可继承的:display、margin、border、padding、background、height、min-height、max-height、width、min-width、max-width、overflow、position、top、bottom、left、right、z-index、float、clear、 table-layout、vertical-align、page-break-after、page-bread-before和unicode-bidi。
所有元素可继承的:visibility和cursor
终极块级元素可继承的:text-indent和text-align
内联元素可继承的:letter-spacing、word-spacing、white-space、line-height、color、font、font-family、font-size、font-style、font-variant、font-weight、text-decoration、text-transform、direction
列表元素可继承的:list-style、list-style-type、list-style-position、list-style-image
@charset、@import、@namespace、@document、@font-face、@keyframes、@media、@page、@supports
伪类和伪元素的根本区别在于:它们是否创造了新的元素(抽象)。从我们模仿其意义的角度来看,如果需要添加新元素加以标识的,就是伪元素,反之,如果只需要在既有元素上添加类别的,就是伪类。伪元素在一个选择器里只能出现一次,并且只能出现在末尾;伪类则是像真正的类一样发挥着类的作用,没有数量上的限制,只要不是相互排斥的伪类,也可以同时使用在相同的元素上。
实际使用
伪类用一个冒号表示 :first-child 伪元素则使用两个冒号表示 ::first-line
默认定位,普通流定位。
浮动定位:有两个取值:left(左浮动)和right(右浮动)。浮动元素会在没有浮动元素的上方,效果上看是遮挡住了没有浮动的元素,有float样式规则的元素是脱离文档流的,它的父元素的高度并不能有它撑开。
相对定位:position:relative,相对本元素的左上角进行定位,top,left,bottom,right都可以有值。虽然经过定位后,位置可能会移动,但是本元素并没有脱离文档流,还占有原来的页面空间。可以设置z-index。使本元素相对于文档流中的元素,或者脱离文档流但是z-index的值比本元素的值要小的元素更加靠近用户的视线。
绝对定位):position:absolute,相对于祖代中有relative(相对定位)并且离本元素层级关系上是最近的元素的左上角进行定位,如果在祖代元素中没有有relative定位的,就默认相对于body进行定位。绝对定位是脱离文档流的,与浮动定位是一样的效果,会压在非定位元素的上方。可以设置z-index属性。
CSS雪碧的基本原理是把你的网站上用到的一些图片整合到一张单独的图片中,从而减少你的网站的HTTP请求数量。该图片使用CSS background和background-position属性渲染,这也就意味着你的标签变得更加复杂了,图片是在CSS中定义,而非img标签。
利用css3新增属性transform: translateX(-50%);
使用flexbox布局,只需要给待处理的块状元素的父元素添加属性 display: flex; justify-content: center;
只需要设置单行行内元素的"行高等于盒子的高"即可;
使用给父元素设置display:table-cell;**和**vertical-align: middle;属即可;
首先设置父元素为相对定位,再设置子元素为绝对定位,设置子元素的top: 50%,即让子元素的左上角垂直居中;
设置绝对子元素的margin-top: -元素高度的一半px;或者设置transform: translateY(-50%);
利用css3新增属性transform: translateY(-50%);
使用flexbox布局,只需要给待处理的块状元素的父元素添加属性 display: flex; align-items: center;
方法一:
设置父元素为相对定位,给子元素设置绝对定位,top: 0; right: 0; bottom: 0; left: 0; margin: auto;
方法二:
设置父元素为相对定位,给子元素设置绝对定位,left: 50%; top: 50%; margin-left: - 元素宽度的一半px;margin-top: - 元素高度的一半px;
方法一:
使用定位属性,设置父元素为相对定位,给子元素设置绝对定位,left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%);
方案二:
使用flex布局实现,设置父元素为flex定位,justify-content: center; align-items: center;
BFC直译为块级格式化上下文,它是一个独立的渲染区域,只有Block-level box参与,它规定了内部的Block-level Box如何布局,并且与外部毫不相干。注意:可以把BFC理解为一个大的盒子,其内部是由Block-level box组成的。
BFC内部的元素和外部的元素绝对不会互相影响,因此, 当BFC外部存在浮动时,它不应该影响BFC内部Box的布局,BFC会通过变窄,而不与浮动有重叠。同样的,当BFC内部有浮动时,为了不影响外部元素的布局,BFC计算高度时会包括浮动的高度。避免margin重叠也是这样的一个道理。
对于私有属性的顺序要注意,把标准写法放到最后,兼容性写法放到前面。
应用场景: 或
的内容,适用高度和宽度,类似于background-size。 object-fit: contain/cover/none/scale-down/fit JS加载方式正常模式 这种情况下 JS 会阻塞 DOM 渲染,浏览器必须等待 index.js 加载和执行完成后才能去做其它事情。 async 模式 async 模式下,它的加载是异步的,JS 不会阻塞 DOM 的渲染, 加载是无顺序的,当它加载结束,JS 会立即执行。使用场景:若该 JS 资源与 DOM 元素没有依赖关系,也不会产生其他资源所需要的数据时,可以使用async 模式,比如埋点统计、广告等。 defer 模式 defer 模式下,JS 的加载也是异步的也不会阻塞 DOM 的渲染,defer 资源会在 DOMContentLoaded 执行之前,并且 defer 是有顺序的加载。如果有多个设置了 defer 的 script 标签存在,则会按照引入的前后顺序执行,即便是后面的 script 资源先返回。所以 defer 可以用来控制 JS 文件的执行顺序,比如 antd.js 和 react.js,因为 antd.js 依赖于 react.js,所以必须先引入 react.js,再引入 antd.js。 module 模式 在主流的现代浏览器中,script 标签的属性可以加上 type="module",浏览器会对其内部的 import 引用发起 HTTP 请求,获取模块内容。这时 script 的行为会像是 defer 一样,在后台下载,并且等待 DOM 解析。 preload 预加载 表示用户十分有可能需要在当前浏览中加载目标资源,所以浏览器必须预先获取和缓存对应资源。preload 加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞 onload 事件;preload 加载的 JS 脚本其加载和执行的过程是分离的,即 preload 会预加载相应的脚本代码,待到需要时自行调用。 prefetch预请求 prefetch 是利用浏览器的空闲时间,加载页面将来可能用到的资源的一种机制;通常可以用于加载其他页面(非首页)所需要的资源,以便加快后续页面的打开速度。 Web Worker 优化长任务在HTML5的新规范中,实现了 Web Worker 来引入 js 的 “多线程” 技术, 可以让我们在页面主运行的 js 线程中,加载运行另外单独的一个或者多个 js 线程 由于浏览器 GUI 渲染线程与 JS 引擎线程是互斥的关系,当页面中有很多长任务时,会造成页面 UI 阻塞,出现界面卡顿、掉帧等情况。 原则上,运算时间超过50ms会造成页面卡顿,属于Long task,这种情况就可以考虑使用Web Worker,但还要先考虑通信时长的问题,假如一个运算执行时长为100ms, 但是通信时长为300ms, 用了Web Worker可能会更慢。一句话: Web Worker专门处理复杂计算的,从此让前端拥有后端的计算能力。 数据类型JavaScript共有八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。 其中 Symbol 和 BigInt 是ES6 中新增的数据类型: - Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。 - BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。 这些数据可以分为原始数据类型和引用数据类型: - 栈:原始数据类型(Undefined、Null、Boolean、Number、String、Symbol、BigInt) - 堆:引用数据类型(对象、数组和函数) 两种类型的区别在于存储位置的不同: - 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储; - 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。 堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中: - 在数据结构中,栈中数据的存取方式为先进后出。 - 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。 在操作系统中,内存被分为栈区和堆区: - 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 - 堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。 数据类型检测typeof- `typeof` 返回一个变量的类型字符串,`instanceof` 返回的是一个布尔值。 - `typeof` 可以精准地判断原始类型,但是不能精准地判断 `null` 和 引用类型,判断引用类型时,除了 `function` 类型,其他的无法准确判断。 console.log(typeof 2); // number console.log(typeof true); // boolean console.log(typeof 'str'); // string console.log(typeof []); // object console.log(typeof function(){}); // function console.log(typeof {}); // object console.log(typeof undefined); // undefined console.log(typeof null); // object // 数值 typeof 37 === 'number' typeof 3.14 === 'number' typeof (42) === 'number' typeof Math.LN2 === 'number' typeof Infinity === 'number' typeof NaN === 'number' // 尽管它是 "Not-A-Number" (非数值) 的缩写 typeof Number(1) === 'number' // Number 会尝试把参数解析成数值 typeof 42n === 'bigint' // 字符串 typeof '' === 'string' typeof 'bla' === 'string' typeof 'template literal' === 'string' typeof '1' === 'string' // 注意内容为数字的字符串仍是字符串 typeof (typeof 1) === 'string' // typeof 总是返回一个字符串 typeof String(1) === 'string' // String 将任意值转换为字符串,比 toString 更安全 // 布尔值 typeof true === 'boolean' typeof false === 'boolean' typeof Boolean(1) === 'boolean' // Boolean() 会基于参数是真值还是虚值进行转换 typeof !!(1) === 'boolean' // 两次调用 ! (逻辑非) 操作符相当于 Boolean() // Symbols typeof Symbol() === 'symbol' typeof Symbol('foo') === 'symbol' typeof Symbol.iterator === 'symbol' // Undefined typeof undefined === 'undefined' let a typeof a === 'undefined' // declaredButUndefinedVariable typeof undeclaredVariable === 'undefined' // undeclaredVariable // Null typeof null === 'object' // 历史bug // 对象 typeof { a: 1 } === 'object' // 使用 Array.isArray 或者 Object.prototype.toString.call // 区分数组和普通对象 typeof [1, 2, 4] === 'object' typeof new Date() === 'object' typeof /regex/ === 'object' // 正则表达式也是对象 // 下面的例子令人迷惑,非常危险,没有用处。避免使用它们。 typeof new Boolean(true) === 'object' typeof new Number(1) === 'object' typeof new String('abc') === 'object' // 函数 typeof function () {} === 'function' typeof class C {} === 'function' typeof Math.sin === 'function' 其中数组、对象、null都会被判断为object,其他判断都正确。 如果想要判断一个变量是否存在,可以使用typeof,使用if(a)若a未声明则报错。 if (a) { // Uncaught ReferenceError: a is not defined } if (typeof a !== 'undefined') { // 这样不会报错 } instanceofinstanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。 class Person {} class Student extends Person {} const s1 = new Student() s1 instanceof Student // true s1 instanceof Person // true s1 instanceof Object // true 也可以用来判断类型,不过一定要判断对象实例的时候才是正确的。 const simpleStr = 'This is a simple string' const myString = new String() const newStr = new String('String created with constructor') simpleStr instanceof String // false, 非对象实例,因此返回 false,无法判断原始类型 myString instanceof String // true newStr instanceof String // true 使用 instanceof 可以精准地判断引用类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。 console.log(2 instanceof Number); // false console.log(true instanceof Boolean); // false console.log('str' instanceof String); // false console.log([] instanceof Array); // true console.log(function(){} instanceof Function); // true console.log({} instanceof Object); // true [] instanceof Array // true const fn = function() {} fn instanceof Function // true const date = new Date() date instanceof Date // true const re = new /abc/ re instanceof RegExp // true 可以看到,instanceof只能正确判断引用数据类型,而不能判断基本数据类型。instanceof 运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。 constructorconsole.log((2).constructor === Number); // true console.log((true).constructor === Boolean); // true console.log(('str').constructor === String); // true console.log(([]).constructor === Array); // true console.log((function() {}).constructor === Function); // true console.log(({}).constructor === Object); // true constructor有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了。 function Fn(){}; Fn.prototype = new Array(); var f = new Fn(); console.log(f.constructor===Fn); // false console.log(f.constructor===Array); // true Object.prototype.toString.call()Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型: var a = Object.prototype.toString; console.log(a.call(2)); console.log(a.call(true)); console.log(a.call('str')); console.log(a.call([])); console.log(a.call(function(){})); console.log(a.call({})); console.log(a.call(undefined)); console.log(a.call(null)); 同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。 判断数组Object.prototype.toString.call()Object.prototype.toString.call(obj).slice(8,-1) === 'Array'; 原型链obj.__proto__ === Array.prototype; Array.isArray()Array.isArrray(obj); instanceofobj instanceof Array Array.prototype.isPrototypeOfArray.prototype.isPrototypeOf(obj) 数组去重Setfunction unique (arr) { return Array.from(new Set(arr)) } var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]; console.log(unique(arr)) //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}] [...new Set(arr)] Mapthis事件可以通过 JavaScript 的 Event 对象来阻止事件捕获。例如,可以使用 event.preventDefault() 方法来阻止事件的默认行为,也可以使用 event.stopPropagation() 方法来阻止事件继续向下传播。 冒泡事件冒泡(Event Bubbling)是指当一个元素触发了某个事件时,该事件会从最具体的元素开始逐级向上传播到较为不具体的元素(也就是从子元素向父元素方向传播),直到传播到文档的根节点为止。这种传播方式就像气泡从水底冒出水面一样,所以叫做事件冒泡。 捕获事件捕获(Event Capturing)是一种处理事件的方式,与事件冒泡相反。事件捕获从文档根节点开始,逐级向下传播到最具体的元素,也就是从父元素向子元素方向传播。 与事件冒泡不同,事件捕获在元素本身触发事件之前被触发。具体来说,事件会从文档根节点开始向下传播,直到达到触发该事件的最具体的元素。这个过程中,每个元素都可以在自己的事件处理程序中处理该事件。 代理(委托)闭包变量(数据)私有化 function count() { let i = 0; function fun() { i ++; console.log(i); } return fn; } const fun = count(); fun(); // 1 fun(); // 2 i = 1000; fun(); // 3 内存泄漏 全局变量在函数执行完不销毁,局部变量在函数执行完销毁 防抖防抖就是指连续触发事件但是在设定的一段时间内中只执行最后一次。例如:设定1000毫秒执行,当你触发事件了,他会1000毫秒后执行,但是在还剩500毫秒的时候你又触发了事件,那就会重新开始1000毫秒之后再执行。记忆核心:重新开始。 应用场景: 1. 搜索框搜索输入 2. 文本编辑器实时保存 实现思路: 定时器 let timerId = null document.querySelector('.ipt').onkeyup = function () { if (timerId !== null) { clearTimeout (timerId) } timerId = setTimeout(() => { console.log('我是防抖') }, 1000) } 节流节流就是指连续触发事件但是在设定的一段时间内中只执行一次函数。例如:设定1000毫秒执行,那你在1000毫秒触发在多次,也只在 1000毫秒后执行一次。记忆核心:不要打断我。 应用场景: 1. 下拉加载 2. 视频播放记录时间等 3. 高频事件,例如快速点击、鼠标滑动、resize事件、scroll事件 let timerId = null document.queryselector (*.ipt*).onmouseover = function () { if (timerId !== null) { return } timerId = setTimeout (() =>{ console.log('我是节流') timerId= null }, 100) } 总结: 防抖:单位时间内。频繁触发事件,只执行最后一次 典型场景:搜索框搜索输入 代码思路是利用定时器,每次触发先清掉以前的定时器(从新开始) 节流:单位时间内,频繁触发事件,只执行一次 典型场景:高频事件 快速点击、鼠标滑动、resize 事件、Scroll事件 代码思路也是利用定时器,等定时器执行完毕,才开启定时器(不要打断) 开发一般用lodash库,利用里面的debounce(防抖)和throttle(节流)来做。 事件循环eventloop,js单线程,同步,异步耗时,执行过程 1. JS是单线程,防止代码阻塞,我们把代码(任务):同步和异步 2. 同步代码给js引擎执行,异步代码交给宿主环境 3. 同步代码放入执行栈中,异步代码等待时机成熟送入任务队列排队 4. 执行栈执行完毕,会去任务队列看是否有异步任务,有就送到执行栈执行,反复循环查看执行,这个过程是事件循环(eventloop) 同步任务立即放入js引擎(js主线程)执行,并原地等待结果。 异步任务先放入宿主环境(nodejs/浏览器),不必原地等待结果。不阻塞主线程继续向下执行,异步结果在将来执行。 - setTimeout() - setinterval() - Ajax/Fetch - 事件绑定 - promise then catch 宏任务/微任务promise本身是同步代码,then/catch是异步代码,同步任务放入执行栈执行,任务队列(宏任务),微任务队列。 函数不调用自己不执行。 async本身是同步的,await是异步的,await阻塞下面代码执行,await右侧代码执行完才会继续向下执行,await后面的代码是异步的微任务,放入微任务队列。 宏任务 setTimeout()、setinterval() 微任务 Promise的then/catch async await Promise拷贝浅拷贝和深拷贝都是创建一份数据的拷贝。 JS 分为原始类型和引用类型,对于原始类型的拷贝,并没有深浅拷贝的区别,我们讨论的深浅拷贝都只针对引用类型。 - 浅拷贝和深拷贝都复制了值和地址,都是为了解决引用类型赋值后互相影响的问题。 - 但是浅拷贝只进行一层复制,深层次的引用类型还是共享内存地址,原对象和拷贝对象还是会互相影响。 - 深拷贝就是无限层级拷贝,深拷贝后的原对象不会和拷贝对象互相影响。 前置知识 两个对象指向同一地址, 用 == 运算符作比较会返回 true。 const obj = {} const newObj = obj console.log(obj == newObj) // true 两个对象指向不同地址, 用 == 运算符作比较会返回 false。 const obj = {} const newObj = {} console.log(obj == newObj) // false 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的是内存地址 。 如果不进行深拷贝,其中一个对象改变了对象的值,就会影响到另一个对象的值。 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。 浅拷贝Object.assignconst obj = { name: 'lin' } const newObj = Object.assign({}, obj) obj.name = 'xxx' // 改变原来的对象 console.log(newObj) // { name: 'lin' } 新对象不变 console.log(obj == newObj) // false 两者指向不同地址 Array.prototype.slice()const arr = ['lin', 'is', 'handsome'] const newArr = arr.slice(0) arr[2] = 'rich' // 改变原来的数组 console.log(newArr) // ['lin', 'is', 'handsome'] console.log(arr == newArr) // false 两者指向不同地址Array.prototype.concat()const arr = ['lin', 'is', 'handsome'] const newArr = [].concat(arr) arr[2] = 'rich' // 改变原来的数组 console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变 console.log(arr == newArr) // false 两者指向不同地址 Array.fromconst arr = ['lin', 'is', 'handsome'] const newArr = Array.from(arr) arr[2] = 'rich' // 改变原来的数组 console.log(newArr) // ['lin', 'is', 'handsome'] console.log(arr == newArr) // false 两者指向不同地址 扩展运算符const arr = ['lin', 'is', 'handsome'] const newArr = [...arr] arr[2] = 'rich' // 改变原来的数组 console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变 console.log(arr == newArr) // false 两者指向不同地址 const obj = { name: 'lin' } const newObj = { ...obj } obj.name = 'xxx' // 改变原来的对象 console.log(newObj) // { name: 'lin' } // 新对象不变 console.log(obj == newObj) // false 两者指向不同地址 深拷贝JSON.parse(JSON.stringify(obj))一般情况下对普通对象需要进行深拷贝,可以使用这种方法进行深拷贝操作,这种是最简单且代码量最少的深拷贝方法。 let a = {a:1,b:2} let b = JSON.parse(JSON.stringify(a)) a.a = 11 console.log(a) //{a:1,b:2} console.log(b) //{a:11,b:2} 缺陷是会忽略undefined、symbol和函数。取不到值为 undefined 的 key;如果对象里有函数,函数无法被拷贝下来;无法拷贝copyObj对象原型链上的属性和方法;对象转变为 date 字符串。 let a = { name: 'Jack', age: 18, hobbit: ['sing', {type: 'sports', value: 'run'}], score: { math: 'A', }, run: function() {}, walk: undefined, fly: NaN, cy: null, date: new Date() } let b = JSON.parse(JSON.stringify(a)) console.log(b) // { // age: 18, // cy: null, // date: "2022-05-15T08:04:06.808Z" // fly: null, // hobbit: (3) ["dance", "sing", {…}], // name: "Jack", // score: {math: "A"}, // } 递归函数const deepClone = (source, cache) => { if(!cache){ cache = new Map() } if(source instanceof Object) { // 不考虑跨 iframe if(cache.get(source)) { return cache.get(source) } let result if(source instanceof Function) { if(source.prototype) { // 有 prototype 就是普通函数 result = function(){ return source.apply(this, arguments) } } else { result = (...args) => { return source.call(undefined, ...args) } } } else if(source instanceof Array) { result = [] } else if(source instanceof Date) { result = new Date(source - 0) } else if(source instanceof RegExp) { result = new RegExp(source.source, source.flags) } else { result = {} } cache.set(source, result) for(let key in source) { if(source.hasOwnProperty(key)){ result[key] = deepClone(source[key], cache) } } return result } else { return source } } jquery.extend()方法// 可以使用
这里的 key="item.id"
告诉 Vue 每个 <li>
元素的唯一标识是 item.id
,从而在更新时更高效地比较和重用 DOM 节点。 正确地管理组件状态当渲染动态列表时,使用相同的组件而不设置 key
,可能导致状态被错误复用。 设置 key
可以确保组件在切换时重新创建,从而避免状态混淆。 如果不设置 key
,切换组件时可能会复用上一个组件的实例及其状态。 支持过渡动画在使用 <transition>
或 <transition-group>
时,key
属性是必要的,用来标识每个需要添加过渡效果的元素。
响应式 指令- 条件渲染指令 `v-if` - 列表渲染指令`v-for` - 属性绑定指令`v-bind` - 事件绑定指令`v-on` - 双向数据绑定指令`v-model` v-show/v-if控制手段:v-show隐藏则是为该元素添加css--display:none,dom元素依旧还在。v-if显示隐藏是将dom元素整个添加或删除 编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换 编译条件:v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染 - `v-show` 由`false`变为`true`的时候不会触发组件的生命周期 - `v-if`由`false`变为`true`的时候,触发组件的`beforeCreate`、`create`、`beforeMount`、`mounted`钩子,由`true`变为`false`的时候触发组件的`beforeDestory`、`destoryed`方法 性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗; 使用场景: - `v-if` 与 `v-show` 都能控制`dom`元素在页面的显示 - `v-if` 相比 `v-show` 开销更大的(直接操作`dom`节点增加与删除) - 如果需要非常频繁地切换,则使用 v-show 较好 - 如果在运行时条件很少改变,则使用 v-if 较好 实例挂载生命周期| 生命周期 | 描述 | | :------------ | :--------------------------------- | | beforeCreate | 组件实例被创建之初 | | created | 组件实例已经完全创建 | | beforeMount | 组件挂载之前 | | mounted | 组件挂载到实例上去之后 | | beforeUpdate | 组件数据发生变化,更新之前 | | updated | 组件数据更新之后 | | beforeDestroy | 组件实例销毁之前 | | destroyed | 组件实例销毁之后 | | activated | keep-alive 缓存的组件激活时 | | deactivated | keep-alive 缓存的组件停用时调用 | | errorCaptured | 捕获一个来自子孙组件的错误时被调用 | 具体分析beforeCreate -> created - 初始化`vue`实例,进行数据观测,该阶段属性和方法都不能使用,此时修改数据不会更新视图。 created - 实例创建完成,完成数据观测,属性与方法的运算,`watch`、`event`事件回调的配置 - 可调用`methods`中的方法,访问和修改data数据触发响应式渲染`dom`,不会触发updated钩子,可通过`computed`和`watch`完成数据计算 - 此时`vm.el`生成的`DOM`替换了`el`选项所对应的`DOM` mounted 把编译好的模版挂载到页面。 - `vm.el`已完成`DOM`的挂载与渲染,此刻打印`vm.el`,发现之前的挂载点及内容已被替换成新的DOM beforeUpdate 组件数据更新之前调用,数据是新的,页面上的数据是旧的,组件即将准备更新和渲染,可以修改数据。 - 更新的数据必须是被渲染在模板上的(`el`、`template`、`render`之一) - 此时`view`层还未更新 - 若在`beforeUpdate`中再次修改数据,不会再次触发更新方法 updated render重新做了渲染,数据和页面都是新的,避免在此阶段修改数据否则可能陷入循环。 - 完成`view`层的更新 - 若在`updated`中再次修改数据,会再次触发更新方法(`beforeUpdate`、`updated`) beforeDestroy - 实例被销毁前调用,此时实例属性与方法仍可访问,可以清除定时器等。 destroyed - 完全销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器 - 并不能清除DOM,仅仅销毁实例 使用场景分析| 生命周期 | 描述 | | :------------ | :----------------------------------------------------------- | | beforeCreate | 执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务 | | created | 组件初始化完毕,各种数据可以使用,常用于异步数据获取 | | beforeMount | 未执行渲染、更新,dom未创建 | | mounted | 初始化结束,dom已创建,可用于获取访问数据和dom元素 | | beforeUpdate | 更新前,可用于获取更新前各种状态 | | updated | 更新后,所有状态已是最新 | | beforeDestroy | 销毁前,可用于一些定时器或订阅的取消 | | destroyed | 组件已销毁,作用同上 | 数据请求在created和mouted的区别created是在组件实例一旦创建完成的时候立刻调用,这时候页面dom节点并未生成;mounted是在页面dom节点渲染完毕之后就立刻执行的。触发时机上created是比mounted要更早的,两者的相同点:都能拿到实例对象的属性和方法。 讨论这个问题本质就是触发的时机,放在mounted中的请求有可能导致页面闪动(因为此时页面dom结构已经生成),但如果在页面加载前完成请求,则不会出现此情况。建议对页面内容的改动放在created生命周期当中。 组件间通信1. 通过 props 传递 2. 通过 emit 触发自定义事件 3. 使用 ref 4. EventBus 5. parent 或root 6. attrs 与 listeners 7. Provide 与 Inject 8. Vuex 父传子props- 适用场景:父组件传递数据给子组件 - 子组件设置`props`属性,定义接收父组件传递过来的参数 - 父组件在使用子组件标签中通过字面量来传递值 Children.vue组件 props:{ // 字符串形式 name:String // 接收的类型参数 // 对象形式 age:{ type:Number, // 接收的类型为数值 defaule:18, // 默认值为18 require:true // age属性必须传递 } } Father.vue组件 ref- 父组件在使用子组件的时候设置`ref` - 父组件通过设置子组件`ref`来获取数据 父组件 this.refs.foo // 获取子组件实例,通过子组件实例我们就能拿到对应的数据 子传父on- 适用场景:子组件传递数据给父组件 - 子组件通过`emit触发`自定义事件,`emit`第二个参数为传递的数值 - 父组件绑定监听器获取到子组件传递过来的参数 Chilfen.vue this.emit('add', good) Father.vue 兄弟组件EventBus- 使用场景:兄弟组件传值 - 创建一个中央事件总线`EventBus` - 兄弟组件通过`emit`触发自定义事件,`emit`第二个参数为传递的数值 - 另一个兄弟组件通过`on`监听自定义事件 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)); } } } // main.js Vue.prototype.bus = new Bus() // 将bus挂载到vue实例的原型上 // 另一种方式 Vue.prototype.bus = new Vue() // Vue已经实现了Bus的功能 Children1.vue this.bus.emit('foo') Children2.vue this.bus.on('foo', this.handle) child/root- 通过共同祖辈`parent`或者`root`搭建通信桥连 兄弟组件 this.parent.on('add',this.add) 另一个兄弟组件 this.parent.emit('add') 注意事项 1. 避免过度依赖:`parent` 和 `root` 会导致组件之间的强耦合,降低代码的可维护性。如果可能,尽量使用更规范的方式(如 Vuex、provide/inject、事件总线等)实现组件通信。 2. 不能访问祖先组件:`parent` 只指向直接的父组件,无法访问更远的祖先组件。 3. 访问顺序问题:如果父组件的属性或方法在子组件挂载时尚未初始化,可能导致访问错误。 listeners- 适用场景:祖先传递数据给子孙 - 设置批量向下传属性`attrs`和 `listeners` - 包含了父级作用域中不作为 `prop` 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。 - 可以通过 `v-bind="
{{$attrs.foo}}
// parent // 给Grandson隔代传值,communication/index.vue // Child2做展开 // Grandson使⽤
{{msg}}
总结对比 | 属性 | `attrs` | `listeners` | | ------------ | ------------------------------------------- | ------------------------------------- | | **包含内容** | 未声明为 `props` 的所有属性。 | 父组件传递的所有事件监听器。 | | **常用场景** | 透传非 `props` 属性到子组件或 HTML 元素。 | 透传事件监听器到子组件或 HTML 元素。 | | **Vue 版本** | Vue 2 和 Vue 3 均支持(Vue 3 中包含事件)。 | Vue 2 支持,Vue 3 已合并到 `attrs`。 | provide 与 inject用于跨组件层级通信的 API,常用于祖先组件与后代组件之间共享数据或方法,而无需逐层通过 props 和事件进行传递。 - 在祖先组件定义`provide`属性,返回传递的值 - 在后代组件通过`inject`接收组件传递过来的值 祖先组件 provide(){ return { foo:'foo' } } 后代组件 inject:['foo'] // 获取到祖先组件传递过来的值 注意事项 - `provide` 和 `inject` 是基于组件的层级关系,必须在祖先与后代之间使用。 - 如果组件层级很深或通信复杂,建议使用 Vuex 或 Pinia 等全局状态管理工具。 - 由于默认不响应式,如果需要频繁更新数据,建议结合响应式对象或其他方式。 总结 | 特性 | `provide` | `inject` | | ---------- | ---------------------------------------- | ---------------------------------- | | 作用 | 提供数据或方法给后代组件 | 接收祖先组件提供的数据或方法 | | 定义位置 | 祖先组件 | 后代组件 | | 响应性支持 | 默认不支持,但可结合 `ref` 或 `reactive` | 可接收响应式数据 | | 场景 | 适合跨多层组件传递数据 | 适合从祖先组件中接收共享数据或方法 | vuex- 适用场景: 复杂关系的组件数据传递 - Vuex作用相当于一个用来存储共享变量的容器 - state用来存放共享变量的地方 - getter,可以增加一个getter派生状态,(相当于store中的计算属性),用来获得共享变量的值 - mutations用来存放修改state的方法。 - actions也是用来存放修改state的方法,不过action是在mutations的基础上进行。常用来做一些异步操作 小结 - 父子关系的组件数据传递选择 `props` 与 `emit`进行传递,也可选择`ref` - 兄弟关系的组件数据传递可选择`bus`,其次可以选择`parent`进行传递 - 祖先与后代组件数据传递可选择`attrs`与`listeners`或者 `Provide`与 `Inject` - 复杂关系的组件数据传递可以通过`vuex`存放共享的变量 双向绑定数据双向绑定就是让数据Model展示到视图View上,同时视图View的变化改变数据Model。在Vue中很轻松就能实现输入框绑定数据,并随输入而动态改变数据。
{{msg}}
架构模式- MVC:通过业务逻辑的Controller层,对应用数据Model进行更改,然后在View视图展示(jsp页面,ejs嵌入式js模板引擎)。 - MVP:P指的是Presenter,负责View和Model之间数据流,作为二者之间的中间人 - MVVM:MVVM 可以分解成(Model-View-ViewModel),ViewModel是P的进阶版,通过事件监听响应View中用户修改Model的数据,减少DOM的操作。 它们设计的目标都是为了解决Model和View的耦合问题。 目前前端框架基本上都是采用 MVVM 模式实现双向绑定,Vue 自然也不例外。但是各个框架实现双向绑定的方法略有所不同,目前大概有三种实现方式。 - 发布订阅模式 - 数据劫持 - Angular脏查机制 hash/history1. hash的路由地址上有#号,history模式没有 2. 在做回车刷新的时候,hash模式会加载对应页面,history会报错484 3. hash模式支持低版本浏览器,history不支持,因为是H5新增的API 4. hash不会重新加载页面,单页面应用必备 5. history有历史记录,H5新增了pushstate和replacestate()去修改历史记录,并不会立刻发送请求 6. history需要后台配置 路由传参Keep-alivekeep-alive`是`vue`中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染`DOM keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们 keep-alive
可以设置以下props
属性: - `include` - 字符串或正则表达式。只有名称匹配的组件会被缓存 - `exclude` - 字符串或正则表达式。任何名称匹配的组件都不会被缓存 - `max` - 数字。最多可以缓存多少组件实例 关于keep-alive
的基本用法: 使用includes
和exclude
: 匹配首先检查组件自身的 name
选项,如果 name
选项不可用,则匹配它的局部注册名称 (父组件 components
选项的键值),匿名组件不能被匹配 设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated
与deactivated
): - 首次进入组件时:`beforeRouteEnter` > `beforeCreate` > `created`> `mounted` > `activated` > ... ... > `beforeRouteLeave` > `deactivated` - 再次进入组件时:`beforeRouteEnter` >`activated` > ... ... > `beforeRouteLeave` > `deactivated` 使用场景使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive
举个栗子: 当我们从首页
–>列表页
–>商详页
–>再返回
,这时候列表页应该是需要keep-alive
从首页
–>列表页
–>商详页
–>返回到列表页(需要缓存)
–>返回到首页(需要缓存)
–>再次进入列表页(不需要缓存)
,这时候可以按需来控制页面的keep-alive
在路由中设置keepAlive
属性判断是否需要缓存 { path: 'list', name: 'itemList', // 列表页 component (resolve) { require(['@/pages/item/list'], resolve) }, meta: { keepAlive: true, title: '列表页' } } 使用<keep-alive>
Vue 在更新 DOM
时是异步执行的。当数据发生变化,Vue
将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新 举例一下 Html
结构
{{ message }}
构建一个vue实例 const vm = new Vue({ el: '#app', data: { message: '原始值' } }) 修改message this.message = '修改后的值1' this.message = '修改后的值2' this.message = '修改后的值3' 这时候想获取页面最新的DOM节点,却发现获取到的是旧值 console.log(vm.el.textContent) // 原始值 这是因为message数据在发现变化的时候,vue并不会立刻去更新Dom,而是将修改数据的操作放在了一个异步操作队列中 如果我们一直修改相同数据,异步操作队列还会进行去重 等待同一事件循环中的所有数据变化完成之后,会将队列中的事件拿来进行处理,进行DOM的更新 为什么要有nexttick 举个例子 {{num}} for(let i=0; i<100000; i++){ num = i } 如果没有 nextTick 更新机制,那么 num 每次更新值都会触发视图更新(上面这段代码也就是会更新10万次视图),有了nextTick机制,只需要更新一次,所以nextTick本质是一种优化策略。 使用场景 如果想要在修改数据后立刻得到更新后的DOM结构,可以使用Vue.nextTick() 第一个参数为:回调函数(可以获取最近的DOM结构) 第二个参数为:执行函数上下文 // 修改数据 vm.message = '修改后的值' // DOM 还没有更新 console.log(vm.el.textContent) // 修改后的值 }) 组件内使用 vm.nextTick() 实例方法只需要通过this.nextTick(),并且回调函数中的 this 将自动绑定到当前的 Vue 实例上 this.message = '修改后的值' console.log(this.el.textContent) // => '原始的值' this.nextTick(function () { console.log(this.el.textContent) // => '修改后的值' }) nextTick() 会返回一个 Promise 对象,可以是用async/await完成相同作用的事情 this.message = '修改后的值' console.log(this.el.textContent) // => '原始的值' await this.nextTick() console.log(this.el.textContent) // => '修改后的值' mixinMixin是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin类的方法而不必成为其子类。 Mixin类通常作为功能模块使用,在需要该功能时“混入”,有利于代码复用又避免了多继承的复杂。 mixin(混入),提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。本质其实就是一个js对象,它可以包含我们组件中任意功能选项,如data、components、methods、created、computed等等。我们只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来,在Vue中我们可以局部混入跟全局混入。 局部混入定义一个mixin对象,有组件options的data、methods属性 var myMixin = { created: function () { this.hello() }, methods: { hello: function () { console.log('hello from mixin!') } } } 组件通过mixins属性调用mixin对象 Vue.component('componentA',{ mixins: [myMixin] }) 该组件在使用的时候,混合了mixin里面的方法,在自动执行created生命钩子,执行hello方法 全局混入通过Vue.mixin()进行全局的混入 Vue.mixin({ created: function () { console.log("全局混入") } }) 使用全局混入需要特别注意,因为它会影响到每一个组件实例(包括第三方组件)。全局混入常用于插件的编写。 注意事项当组件存在与mixin对象相同的选项的时候,进行递归合并的时候组件的选项会覆盖mixin的选项 但是如果相同选项为生命周期钩子的时候,会合并成一个数组,先执行mixin的钩子,再执行组件的钩子 使用场景在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立 这时,可以通过Vue的mixin功能将相同或者相似的代码提出来 举个例子 定义一个modal弹窗组件,内部通过isShowing来控制显示 const Modal = { template: '#modal', data() { return { isShowing: false } }, methods: { toggleShow() { this.isShowing = !this.isShowing; } } } 定义一个tooltip提示框,内部通过isShowing来控制显示 const Tooltip = { template: '#tooltip', data() { return { isShowing: false } }, methods: { toggleShow() { this.isShowing = !this.isShowing; } } } 通过观察上面两个组件,发现两者的逻辑是相同,代码控制显示也是相同的,这时候mixin就派上用场了 首先抽出共同代码,编写一个mixin const toggle = { data() { return { isShowing: false } }, methods: { toggleShow() { this.isShowing = !this.isShowing; } } } 两个组件在使用上,只需要引入mixin const Modal = { template: '#modal', mixins: [toggle] }; const Tooltip = { template: '#tooltip', mixins: [toggle] } slot在HTML中 slot 元素 ,作为 Web Components 技术套件的一部分,是Web组件内的一个占位符 该占位符可以在后期使用自己的标记语言填充,举个栗例子: Slot template12template不会展示到页面中,需要用先获取它的引用,然后添加到DOM中, customElements.define('element-details', class extends HTMLElement { constructor() { super(); const template = document .getElementById('element-details-template') .content; const shadowRoot = this.attachShadow({mode: 'open'}) .appendChild(template.cloneNode(true)); } }) 在Vue中的概念也是如此 Slot 艺名插槽,花名“占坑”,我们可以理解为solt在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中slot位置),作为承载分发内容的出口 可以将其类比为插卡式的FC游戏机,游戏机暴露卡槽(插槽)让用户插入不同的游戏磁条(自定义内容) slot可以分来以下三种: - 默认插槽 - 具名插槽 - 作用域插槽 默认插槽子组件用<slot>标签来确定渲染的位置,标签里面可以放DOM结构,当父组件使用的时候没有往插槽传入内容,标签内DOM结构就会显示在页面 父组件在使用的时候,直接在子组件的标签内写入内容即可 子组件Child.vue
插槽后备的内容
父组件
默认插槽
具名插槽子组件用name
属性来表示插槽的名字,不传为默认插槽 父组件中在使用时在默认插槽的基础上加上slot
属性,值为子组件插槽name
属性值 子组件Child.vue
插槽后备的内容插槽后备的内容 父组件 具名插槽内容... 作用域插槽子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件v-slot
接受的对象上 父组件中在使用时通过v-slot:
(简写:#)获取子组件的信息,在内容中使用 子组件Child.vue
父组件 来⾃⼦组件数据:{{slotProps.testProps}} 来⾃⼦组件数据:{{slotProps.testProps}} 小结:- `v-slot`属性只能在``上使用,但在只有默认插槽时可以在组件标签上使用 - 默认插槽名为`default`,可以省略default直接写`v-slot` - 缩写为`#`时不能不写参数,写成`#default` - 可以通过解构获取`v-slot={user}`,还可以重命名`v-slot="{user: newName}"`和定义默认值`v-slot="{user = '默认值'}"` 修饰符表单修饰符在我们填写表单的时候用得最多的是input
标签,指令用得最多的是v-model
关于表单的修饰符有如下: - lazy - trim - number lazy在我们填完信息,光标离开标签的时候,才会将值赋予给value
,也就是在change
事件之后再进行信息同步
{{value}}
trim自动过滤用户输入的首空格字符,而中间的空格不会过滤 number自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat
解析,则会返回原来的值 事件修饰符事件修饰符是对事件捕获以及目标进行了处理,有如下修饰符: - stop - prevent - self - once - capture - passive - native stop阻止了事件冒泡,相当于调用了event.stopPropagation
方法
ok
//只输出1 prevent阻止了事件的默认行为,相当于调用了event.preventDefault
方法 self只当在 event.target
是当前元素自身时触发处理函数
...
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self
会阻止所有的点击,而 v-on:click.self.prevent
只会阻止对元素自身的点击 once绑定了事件以后只能触发一次,第二次就不会触发 ok capture使事件触发从包含这个元素的顶层开始往下触发
obj1
obj2
obj3
obj4
// 输出结构: 1 2 4 3 passive在移动端,当我们在监听元素滚动事件的时候,会一直触发onscroll
事件会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给onscroll
事件整了一个.lazy
修饰符
...
不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。passive 会告诉浏览器你不想阻止事件的默认行为。 native让组件变成像html内置标签那样监听根元素的原生事件,否则组件上使用 v-on 只会监听自定义事件 使用.native修饰符来操作普通HTML标签是会令事件失效的 鼠标按钮修饰符鼠标按钮修饰符针对的就是左键、右键、中键点击,有如下: - left 左键点击 - right 右键点击 - middle 中键点击 okokok 键盘修饰符键盘修饰符是用来修饰键盘事件(onkeyup,onkeydown)的,有如下: keyCode存在很多,但vue为我们提供了别名,分为以下两种: - 普通键(enter、tab、delete、space、esc、up...) - 系统修饰键(ctrl、alt、meta、shift...) // 只有按键为keyCode的时候才触发 还可以通过以下方式自定义一些全局的键盘码别名 Vue.config.keyCodes.f2 = 113 v-bind修饰符v-bind修饰符主要是为属性进行操作,用来分别有如下: - async - prop - camel async能对props进行一个双向绑定 //父组件 //子组件 this.emit('update:myMessage',params); 以上这种方法相当于以下的简写 //父亲组件 func(e){ this.bar = e; } //子组件js func2(){ this.emit('update:myMessage',params); } 使用async需要注意以下两点: - 使用`sync`的时候,子组件传递的事件名格式必须为`update:value`,其中`value`必须与子组件中`props`中声明的名称完全一致 - 注意带有 `.sync` 修饰符的 `v-bind` 不能和表达式一起使用 - 将 `v-bind.sync` 用在一个字面量的对象上,例如 `v-bind.sync=”{ title: doc.title }”`,是无法正常工作的 props设置自定义标签属性,避免暴露数据,防止污染HTML结构 camel将命名变为驼峰命名法,如将view-Box属性名转换为 viewBox 应用场景根据每一个修饰符的功能,我们可以得到以下修饰符的应用场景: - .stop:阻止事件冒泡 - .native:绑定原生事件 - .once:事件只执行一次 - .self :将事件绑定在自身身上,相当于阻止事件冒泡 - .prevent:阻止默认事件 - .caption:用于事件捕获 - .once:只触发一次 - .keyCode:监听特定键盘按下 - .right:右键 性能SPA首屏加载减少首屏渲染时间的方法有很多,总的来讲可以分成两大部分 :资源加载优化 和 页面渲染优化。 - 减小入口文件积 - 静态资源本地缓存 - UI框架按需加载 - 图片资源的压缩 - 组件重复打包 - 开启GZip压缩 - 使用SSR 减小入口文件体积常用的手段是路由懒加载,把不同路由对应的组件分割成不同的代码块,待路由被请求的时候会单独打包路由,使得入口文件变小,加载速度大大增加。 在vue-router配置路由的时候,采用动态加载路由的形式。以函数的形式加载路由,这样就可以把各自的路由文件分别打包,只有在解析给定的路由时,才会加载路由组件。 routes:[ path: 'Blogs', name: 'ShowBlogs', component: () => import('./components/ShowBlogs.vue') ] 静态资源本地缓存后端返回资源问题: - 采用`HTTP`缓存,设置`Cache-Control`,`Last-Modified`,`Etag`等响应头 - 采用`Service Worker`离线缓存 前端合理利用localStorage UI框架按需加载在日常使用UI框架,例如element-UI、或者antd,我们经常性直接引用整个UI库。 import ElementUI from 'element-ui' Vue.use(ElementUI) 但实际上我用到的组件只有按钮,分页,表格,输入与警告 所以我们要按需引用 import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui'; Vue.use(Button) Vue.use(Input) Vue.use(Pagination) 组件重复打包假设A.js文件是一个常用的库,现在有多个路由使用了A.js文件,这就造成了重复下载 解决方案:在webpack的config文件中,修改CommonsChunkPlugin的配置 minChunks: 3 minChunks为3表示会把使用3次及以上的包抽离出来,放进公共依赖文件,避免了重复加载组件 图片资源的压缩图片资源虽然不在编码过程中,但它却是对页面性能影响最大的因素,对于所有的图片资源,我们可以进行: - 适当的压缩 - 使用svg图片 - 对页面上使用到的`icon`,可以使用在线字体图标,或者雪碧图,将众多小图标合并到同一张图上,用以减轻`http`请求压力。 开启GZip压缩拆完包之后,我们再用gzip做一下压缩 安装compression-webpack-plugin nmp i compression-webpack-plugin -D 在vue.congig.js中引入并修改webpack配置 const CompressionPlugin = require('compression-webpack-plugin') configureWebpack: (config) => { if (process.env.NODE_ENV === 'production') { // 为生产环境修改配置... config.mode = 'production' return { plugins: [new CompressionPlugin({ test: /\.js|\.html|\.css/, //匹配文件名 threshold: 10240, //对超过10k的数据进行压缩 deleteOriginalAssets: false //是否删除原文件 })] } } 在服务器我们也要做相应的配置 如果发送请求的浏览器支持gzip,就发送给它gzip格式的文件。 使用SSRSSR(Server side ),也就是服务端渲染,组件或页面通过服务器生成html字符串,再发送到浏览器。 虚拟滚动骨架屏http2/3Quic、多路复用gzip压缩托管oss+cdn安全