ES5的5种:Null,undefined,Boolean,Number,String,ES6新增:Symbol表示独一无二的值ES10新增:BigInt表示任意大的整数
一种引用数据类型:(本质上是由一组无序的键值对组成)
引用数据类型:Object。包含Object、Array、function、Date、RegExp。JavaScript不支持创建任何自定义类型的数据,也就是说JavaScript中所有值的类型都是上面8中之一。
要创建BigInt,只需在整数的末尾追加n即可。比较:console.log(9007199254740995n);//→9007199254740995nconsole.log(9007199254740995);//→9007199254740996或者,可以调用BigInt()构造函数BigInt("9007199254740995");//→9007199254740995n//注意最后一位的数字9007199254740992===9007199254740993;//→trueconsole.log(9999999999999999);//→10000000000000000数据类型存储以及堆栈内存是什么基本数据类型:直接存储在栈内存中,占据空间小,大小固定,属于被频繁使用的数据。指的是保存在栈内存中的简单数据段;numberstring布尔引用数据类型:同时存储在栈内存与堆内存中,占据空间大,大小不固定。引用数据:类型将指针存在栈中,将值存在堆中。当我们把对象值赋值给另外一个变量时,复制的是对象的指针,指向同一块内存地址,意思是,变量中保存的实际上只是一个指针,这个指针指向内存堆中实际的值,数组对象
栈:是一种连续储存的数据结构,具有先进后出后进先出的性质。通常的操作有入栈(压栈),出栈和栈顶元素。想要读取栈中的某个元素,就是将其之间的所有元素出栈才能完成。堆:是一种非连续的树形储存数据结构,具有队列优先,先进先出;每个节点有一个值,整棵树是经过排序的。特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。常用来实现优先队列,存取随意。
所有基本类型中Boolean值是false的只有6个,分别是:0NaN‘‘nullundefinedfalse引用类型Boolean值全是true.if条件是单个值时,如果是truly值,条件成立,如果是falsely值,条件不成立
000:object-当前存储的数据指向一个对象。1:int-当前存储的数据是一个31位的有符号整数。010:double-当前存储的数据指向一个双精度的浮点数。100:string-当前存储的数据指向一个字符串。110:boolean-当前存储的数据是布尔值。有两种特殊数据类型:
那也就是说null的类型标签也是000,和Object的类型标签一样,所以会被判定为Object。
事件是文档和浏览器窗口中发生的特定的交互瞬间,事件就发生了。一是直接在标签内直接添加执行语句,二是定义执行函数。addeventlistener监听事件事件类型分两种:事件捕获、事件冒泡。事件捕获就是:网景公司提出的事件流叫事件捕获流,由外往内,从事件发生的顶点开始,逐级往下查找,一直到目标元素。事件冒泡:IE提出的事件流叫做事件冒泡就是由内往外,从具体的目标节点元素触发,逐级向上传递,直到根节点。什么是事件流事件流就是,页面接受事件的先后顺序就形成了事件流。自定义事件自定义事件,就是自己定义事件类型,自己定义事件处理函数。
事件委托,又名事件代理。事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托也就没法实现了阻止事件冒泡event.stopPropagation().stop修饰符addEventListener(‘click’,函数名,true/false)默认值为false(即使用事件冒泡)true事件捕获好处:提高性能,减少了事件绑定,从而减少内存占用应用场景在vue中事件委托:我们经常遇到vue中v-for一个列表,列表的每一项都绑定了@click处理事件。我们都知道绑定这么多监听,从性能方面来说是不太好的。那我们我们可以通过把每个item的click事件委托给父元素的形式来实现
我们在封装这个函数的时候可以用addEventListener(事件监听)来实现,封装的函数有三个参数,第一个是要绑定事件的元素,第二个是要绑定的事件类型,第三个是事件的执行函数。调用这个函数就可以实现给某个元素绑定一个事件了。
作用域:作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。简单说:函数内部局部作用域,函数外面全局作用域。
作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域全局作用域就是Js中最外层的作用域,在哪里都可以访问函数作用域是js通过函数创建的一个独立作用域,只能在函数内部访问,函数可以嵌套,所以作用域也可以嵌套Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)
mouseenter:鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发。mouseover:鼠标进入被绑定事件监听元素节点时触发一次,如果目标元素包含子元素,鼠标移出子元素到目标元素上也会触发。mouseenter不支持事件冒泡mouseover会冒泡
深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。1.浅拷贝:
2.深拷贝:
3、赋值:当我们把一个对象赋值给一个新的变量时,赋的是该对象在栈中的内存地址,而不是堆中的数据。也就是两个对象
具体实现看开头的手写系列
1.直接调用函数名加上括号()2.函数表达式变量名()
函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
arguments当我们不知道有多少个参数传进来的时候就用arguments来接收,是一个类似于数组的对象,他有length属性,可以arguments[i]来访问对象中的元素,但是它不能用数组的一些方法。例如push、pop、slice等。arguments虽然不是一个数组,但是它可以转成一个真正的数组。取之可以用展开运算符来数组和类数组类数组:①拥有length属性,其它属性(索引)为非负整数;箭头函数里没有arguments②不具有数组所具有的方法;③类数组是一个普通对象,而真实的数组是Array类型。常见的类数组:arguments,document.querySelectorAll得到的列表,jQuery对象($(“div”));
在全局的环境下this是指向window的普通函数调用直接调用中的this会指向window,严格模式下this会指向undefined,自执行函数this指向window,定时器中的this指向window在对象里调用的this,指向调用函数的那个对象,在构造函数以及类中的this,构造函数配合new使用,而new关键字会将构造函数中的this指向实例化对象,所以构造函数中的this指向当前实例化的对象方法中的this谁调用就指向谁。箭头函数没有自己的this,箭头函数的this在定义的时候,会继承自外层第一个普通函数的this
函数式编程是一种强调以函数为主的软件开发风格。通过组合纯函数,避免共享状态、可变作用和副作用来构建软件的过程。目的:使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用并减少对状态的改变。
1、闭包的概念就是:只有权利访问另一个函数作用域中的变量,一般就是函数包裹着函数。3、闭包可以重用一个变量,且保证这个变量不会被污染的一种机制。这些变量的值始终保持在内存中,不会被垃圾回收机制处理4、闭包的缺点:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。5、为什么要用闭包:使用场景:防抖、节流、函数套函数避免全局污染
闭包原理函数执行分成两个阶段(预编译阶段和执行阶段)。1.在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,如果已存在“闭包”,则只需要增加对应属性值即可。2.执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,所以内部函数可以继续使用“外部函数”中的变量利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。call、apply、bind封装与区别都是来改变this指向和函数的调,实际上call与apply的功能是相同的,只是两者的传参方式不一样,call法跟的是个参数列表,apply跟个数组作为参数,call法和apply使后就直接调bind传参后不会立即执行,而是返回一个改变了this指向的函数,这个函数可以继续传参,且执行,需要类似于bind()()两个括号才能调。
bind返回的函数可以作为构造函数吗?
不可以,会报错的哦,ERROR>UncaughtTypeError:sisnotaconstructor
概念:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。容易理解的概念:Currying概念其实很简单,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数(主要是利用闭包实现的)。特点:①接收单一参数,将更多的参数通过回调函数来搞定;②返回一个新函数,用于处理所有的想要传入的参数;③需要利用call/apply与arguments对象收集参数;④返回的这个函数正是用来处理收集起来的参数。作用:能进行部分传值,而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参,这个时候柯里化和偏应用就能派上用场。用途:我认为函数柯里化是对闭包的一种应用形式,延迟计算、参数复用、动态生成函数(都是闭包的用途)。
柯里化函数:把一个多参数的函数转化为单参数函数的方法。并且返回接受余下的参数而且返回结果的新函数的技术。我的理解就是将一个接受多个参数的函数,转化为接收一个参数,并且不改变输出结果的一种办法。我觉得这就是js的柯里化函数
//简单的相加函数varadd=function(x,y){returnx+y}//调用:add(1,2)//柯里化以后varadd=function(x){//柯里化函数(闭包)returnfunction(y){returnx+y}}add(1)(2)这样做有什么好处,我得理解是在需要的情况下生成一个中间工具,简化代码,并且清晰代码。
高阶函数只是,将函数作为参数,函数的返回值返回值是函数
1.js单线程
2.js事件循环
js代码执行过程中会有很多任务,这些任务总的分成两类:
需要注意的是除了同步任务和异步任务,任务还可以更加细分为macrotask(宏任务)和microtask(微任务),js引擎会优先执行微任务
微任务包括了promise的回调、node中的process.nextTick、对Dom变化监听的MutationObserver。宏任务包括了script脚本的执行、setTimeout,setInterval,setImmediate一类的定时事件,还有如I/O操作、UI渲染等。
最后可以用下面一道题检测一下收获:
setTimeout(function(){console.log(1)},0);newPromise(function(resolve,reject){console.log(2);resolve()}).then(function(){console.log(3)});process.nextTick(function(){console.log(4)})console.log(5)第一轮:主线程开始执行,遇到setTimeout,将setTimeout的回调函数丢到宏任务队列中,在往下执行newPromise立即执行,输出2,then的回调函数丢到微任务队列中,再继续执行,遇到process.nextTick,同样将回调函数扔到为任务队列,再继续执行,输出5,当所有同步任务执行完成后看有没有可以执行的微任务,发现有then函数和nextTick两个微任务,先执行哪个呢?process.nextTick指定的异步任务总是发生在所有异步任务之前,因此先执行process.nextTick输出4然后执行then函数输出3,第一轮执行结束。第二轮:从宏任务队列开始,发现setTimeout回调,输出1执行完毕,因此结果是25431
JavaScript是单线程(js不走完下面不会走是因为同步)会阻塞DOM的解析,因此也就会阻塞DOM的加载。所以有时候我们希望延迟JS的加载来提高页面的加载速度。1.把JS放在页面的最底部2.script标签的defer属性:脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用(即没有「src」属性的脚本)。3.是在外部JS加载完成后,浏览器空闲时,Load事件触发前执行,标记为async的脚本并不保证按照指定他们的先后顺序执行,该属性对于内联脚本无作用(即没有「src」属性的脚本)。4.动态创建script标签,监听dom加载完毕再引入js文件
宏任务macrotask:可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。常见的宏任务:script,setTimeout,setInterval,setImmediate,I/O,UIrendering。微任务microtask(异步):可以理解是在当前task执行结束后立即执行的任务。常见的微任务:process.nextTick(Nodejs),Promise.then(),MutationObserver。线程,进程线程是最小的执行单元,进程是最小的资源管理单元一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程内存泄露如果那些不再使用的变量,它们所占用的内存不去清除的话就会造成内存泄漏比如说:1、闭包:在闭包中引入闭包外部的变量时,当闭包结束时此对象无法被垃圾回收(GC)。2、DOM:当原有的DOM被移除时,子结点引用没有被移除则无法回收3、Times计时器泄露
2.在执行阶段,就是按照代码的顺序依次执行。
在js中我们经常需要同时执行很多件任务,例如,定时器,事件。异步数据,而js是单线程的原因不能同时进行很多件事情,必须等上一件任务执行完了才会执行下一个,需要通过EventLoop来处理很多任务的执行因为js是单线程的,代码执行的时候,将不同的函数执行上下文压入到栈中进行有序的执行,在执行同步代码的时候,如果遇到了异步事件,js引擎并不会一直等待其返回结果,就是将它挂起,继续执行栈中其他的任务当同步任务执行完了,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。任务队列分为的宏任务队列和微任务队列,当前的执行栈中执行完,js引擎会首先判断微任务队列是否有任务可以执行有的话,放到栈中执行。当微任务队列中的任务执行完了再去判断宏任务中的队列。为什么会有任务队列呢?还是因为javascript单线程的原因,单线程,就意味着一个任务一个任务的执行,执行完当前任务,执行下一个任务,这样也会遇到一个问题,就比如说,要向服务端通信,加载大量数据,如果是同步执行,js主线程就得等着这个通信完成,然后才能渲染数据,为了高效率的利用cpu,就有了同步任务和异步任务之分。
Math.PI圆周率Math.floor()向下取整Math.ceil()向上取整Math.round()四舍五入版就近取整Math.abs()绝对值Math.max()/Math.min()求最大和最小值Math.random()获取范围在[0,1)内的随机值
setTimeout()和setInterval()经常被用来处理延时和定时任务。setTimeout()方法用于在指定的毫秒数后调用函数或计算表达式setInterval()则可以在每隔指定的毫秒数循环调用函数或表达式,直到clearInterval把它清除。
window对象给我们提供了一个history对象,与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中)访问过的URL。history.back可以后退一个网页history.go可以前进后退1前进-1后退history.forward前进
DOM是document用来表示文档中对象的标准模型,他是由节点和对象组成的结构集合。在浏览器解析HTML标签时,会构建一个DOM树结构。
操作说明书
"=="操作符在左右数据不一致的时候,会先进行隐式转换,该值意味着不是基本数据类型,因为如果a是null或者undefined、bool类型都不可能返回true;可以推测a是复杂数据类型。方法一:数组的toString接口默认调用数组的join方法,重新join方法leta=[1,2,3];a.join=a.shift;console.log(a==1&&a==2&&a==3)//true方法二:利用数据劫持(Proxy/Object.definedProperty)leti=1;leta=newProxy({},{i:1,get:function(){return()=>this.i++}});console.log(a==1&&a==2&&a==3);为什么0.1+0.2!==0.3,如何让其相等代码语言:javascript复制在开发过程中遇到类似这样的问题:letn1=0.1,n2=0.2console.log(n1+n2)//0.30000000000000004这里得到的不是想要的结果,要想等于0.3,就要把它进行转化:(n1+n2).toFixed(2)//注意,toFixed为四舍五入复制代码toFixed(num)方法可把Number四舍五入为指定小数位数的数字。那为什么会出现这样的结果呢?计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和es6部i分面试题1、ES6新增特性
js中我们在调函数的时候经常会遇到this作域的问题,这个时候ES6给我们提箭头函数。1、箭头函数是匿名函数不能作为构造函数,不能使用new2、箭头函数不绑定arguments,取而代之用rest参数…解决,3、this指向不同,箭头函数的this在定义的时候继承自外层第一个普通函数的this4、箭头函数通过call()或apply()调用一个函数,只传入了一个参数,对this并没有影响.5、箭头函数没有prototype(原型),所以箭头函数本身没有this6、箭头函数不能当做Generator函数,不能使用yield关键字、7、写法不同,箭头函数把function省略掉了()=>也可以吧return省略调写法更简洁8、箭头函数不能通过call()、apply()、bind()方法直接修改它的this指向。
相同点都是循环遍历数组中的每一项forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组),需要用哪个的时候就写哪个匿名函数中的this都是指向window只能遍历数组注意:forEach对于空数组是不会调用回调函数的。
不同点map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。(原数组进行处理之后对应的一个新的数组。)map()方法不会改变原始数组map()方法不会对空数组进行检测forEach()方法用于调用数组的每个元素,将元素传给回调函数.(没有return,返回值是undefined)
1、Promise是异步编程的一种解决方案,主要用于异步计算,支持链式调用,可以解决回调地狱的问题,自己身上有all、reject、resolve、race等方法,原型上有then、catch等方法。2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果,可以在对象之间传递和操作promise,帮助我们处理队列3、promise有三个状态:pending[待定]初始状态,fulfilled[实现]操作成功,rejected[被否决]操作失败4、Promise对象状态改变:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了5、如果不设置回调函数,Promise内部抛出的错误,不会反应到外部,但是写了then和catch,会被then的第二个参数或catch所捕获
promise的then会返回一个新的promise对象,能保证then方可以进行链式调用
Async和await是一种同步的写法,但还是异步的操作,两个必须配合一起使用函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。await是个运算符,用于组成表达式,await表达式的运算结果取决于它等的东西,如果是promise则会等待promaise返回结果,接普通函数直接进行链式调用.await能够获取promise执行的结果await必须和async一起使用才行,async配合await使用是一个阻塞的异步方法如果await后面不是Promise对象,就直接返回对应的值,只能在async函数中出现,普通函数直接使用会报错await语句后的Promise对象变成reject状态时,那么整个async函数会中断,后面的程序不会继续执行
使用场景:
我在项目中:需求:执行第一步,将执行第一步的结果返回给第二步使用。在ajax中先拿到一个接口的返回数据,然后使用第一步返回的数据执行第二步操作的接口调用,达到异步操作。
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值常见的几种方式有1.默认值2.交换变量3.将剩余数组赋给一个变量结构数组和对象字符串区别对象的解构与数组类似,但有所不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。字符串也是可以解构赋值的。字符串被转换成了一个类似数组的对象.我在项目中:就是从目标对象或数组中提取自己想要的变量。最常用的场景是:element-ui,vant-ui按需引入,请求接口返回数据,提取想要数据。
1、推荐在循环对象属性的时候,使用for…in,在遍历数组的时候的时候使用for…of。2、forin遍历的是数组的索引,而forof遍历的是数组元素值3、for…of不能循环普通的对象,需要通过和Object.keys()搭配使用4、for…in便利顺序以数字为先无法便利symbol属性可以便利到公有中可枚举的5、从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。
promise、Generator、async/await进行比较:
promise和async/await是专门用于处理异步操作的Generator并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口…)promise编写代码相比Generator、async更为复杂化,且可读性也稍差Generator、async需要与promise对象搭配处理异步情况async实质是Generator的语法糖,相当于会自动执行Generator函数async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案
js的构造函数(在别的后台语言上叫做类)上可以添加一些成员,可以在构造函数内部的this上添加,可以在构造函数本身上添加,通过这两种方式添加的成员,就分别称为实例成员和静态成员实例成员:构造函数中this上添加的成员静态成员:构造函数本身上添加的成员实例成员,只能由实例化的对象来访问静态成员,只能由构造函数本身来访问实例化对象的proto指向构造函数的prototype属性指向的对象,实例化的对象可以访问到它后者身上的成员
构造函数生成实例的执行过程:使用面向对象编程时,new关键字做了什么?
set数据的特点是数据是唯一的
constset1=newSet()增加元素使用addset2.add(4)是否含有某个元素使用hasconsole.log(set2.has(2))查看长度使用sizeconsole.log(set2.size)删除元素使用deleteset2.delete(2)size:返回Set实例的成员总数。add(value):添加某个值,返回Set结构本身。delete(value):删除某个值。clear():清除所有成员,没有返回值。Set的不重复性
Map`对比`object`最大的好处就是,key不受`类型限制
symbol是es6加入的,是一个基本数据类型,它代表的是一个独一无二的值,SYMBOL值是由SYMBOL函数生成,也就是说现在我们定义对象的属性名字可以是原有的字符串也可以是symbol类型的,symbol可以保证不与其他属性名冲突,减少了bug的产生,如果那symbol对比的话就是会返回falsesymbol他是一个原始类型的值就,不可以使用new关键字,symbol不是对象没有迭代器的接口不能去添加属性值,他是类似于字符串的一种类型symbol不能用来四则运算,否则会报错,只能用显示的方式转为字符串symbol参数里的a表示一种修饰符对当前创建的symbol的一种修饰,作为区分,否则会混淆
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。其实iteration==iterator有三个作用:
Object.assign可以实现对象的合并。它的语法是这样的:Object.assign(target,...sources)Object.assign会将source里面的可枚举属性复制到target。如果和target的已有属性重名,则会覆盖。同时后续的source会覆盖前面的source的同名属性。Object.assign复制的是属性值,如果属性值是一个引用类型,那么复制的其实是引用地址,就会存在引用共享的问题Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有length属性的对象。
1、将类数组对象转换为真正数组:
我对模块的理解是,一个模块是实现一个特定功能的一组方法。在最开始的时候,js只实现一些简单的功能,所以并没有模块的概念,但随着程序越来越复杂,代码的模块化开发变得越来越重要。由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污染,并且模块间没有联系。后面提出了对象写法,通过将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,但是这种办法会暴露所有的所有的模块成员,外部代码可以修改内部属性的值。现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。
js中现在比较成熟的有四种模块加载方案:
加油快通关了,通关了你会有收获的。
vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调Vue是一个典型的MVVM框架,模型(Model)只是普通的javascript对象,修改它则试图(View)会自动更新。这种设计让状态管理变得非常简单而直观Observer(数据监听器):Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是WatcherCompile(指令解析器):Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新试图Watcher(订阅者):Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:
vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的HTML语法,所有需要将template转化成一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染出对应的HTML元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。Vue的编译过程就是将template转化为render函数的过程分为以下三步第一步是将模板字符串转换成elementASTs(解析器)第二步是对AST进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)第三步是使用elementASTs生成render函数代码字符串(代码生成器)
虚拟DOM,其实就是用对象的方式取代真实的DOM操作,把真实的DOM操作放在内存当中,在内存中的对象里做模拟操作。当页面打开时浏览器会解析HTML元素,构建一颗DOM树,将状态全部保存起来,在内存当中模拟我们真实的DOM操作,操作完后又会生成一颗dom树,两颗DOM树进行比较,根据diff算法比较两颗DOM树不同的地方,只渲染一次不同的地方。(个人理解)虚拟dom他不并不是真实的dom,是根据模板生成一个js对象(使用createElement,方法),根据这个js对象再去生成真实的dom,对复杂的文档DOM结构,提供一种方便的工具,进行最小化的DOM操作,是可以快速的渲染和高效的更新元素,提高浏览器的性能,例如,一个ul标签下很多个li标签,其中只有一个li有变化,这种情况下如果使用新的ul去替代旧的ul,因为这些不必要的DOM操作而造成了性能上的浪费,但是如果直接使用虚拟节点覆盖旧节点的话,减少了很多的不必要的DOM操作。我们在渲染页面的时候会对新的虚拟dom和旧的虚拟dom进行对比只渲染不同的地方,而不再是像之前只要发生变化,全部的真实dom都要重新渲染,所以提高了渲染的效率。
diff算法当data发生改变会根据新的数据生成一个新的虚拟dom,新的虚拟dom和旧的虚拟dom进行对比,这个对比的过程就是diff算法,会找到不同地方,只去渲染不同的地方,总的来说就是减少DOM,重绘和回流。
为什么要用虚拟DOM来描述真实的DOM呢?
创建真实DOM成本比较高,如果用js对象来描述一个dom节点,成本比较低,另外我们在频繁操作dom是一种比较大的开销。所以建议用虚拟dom来描述真实dom。
主要分为四部分
1、observer主要是负责对Vue数据进行递归便利,使其数据拥有get和set方法,当有数据给某个对象值赋值,就触发setter就监听到数据的变化了。(如有变动可拿到最新值并通知订阅者)2、compile指令解析器负责绑定数据和指令解析。将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数。一旦数据有变动,收到通知,更新视图3、订阅者watcher:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是负责数据监听,当数据发生改变,能调用自身的update()方法,并触发Compile中绑定的更新函数4、实现一个订阅器dep:采用发布者订阅者模式,用来收集订阅者的watcher,对监听器observer和订阅者watcher进行统一管理
灵活的组件应用,高效的数据绑定
渐进式代表的含义是:主张最少——它是一个轻量级框架,只做了自己该做的事,没有做不该做的事每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式。这里的vue数据驱动的是视图,也就是DOM元素,指的是让DOM的内容随着数据的改变而改变框架的理解
SSR全称ServerSideRender
SSR的缺点:
1.性能提升更小巧,更快速;支持摇树优化。支持Fragments(支持多个根节点)和跨组件渲染;支持自定义渲染器。2.API变动Vue2使用选项类型API(OptionsAPI)对比Vue3合成型API(CompositionAPI)optionsApi使用传统api中,新增一个需求,要在data,methods,computed中修改compositionApi我们可以更加优雅的组织我们的代码,函数,让我们的代码更加有序的组合在一起3.重写虚拟DOM(VirtualDOMRewrite)随着虚拟DOM重写,减少运行时(runtime)开销。重写将包括更有效的代码来创建虚拟节点。vue3没有了过滤器双向数据绑定从Object.defineProperty()变成了proxy,通过下标修改数组变化了试图数据没发生变化this.$set()vue3不需要
setup函数3.0新加入了TypeScript以及PWA支持默认使用懒加载可以不用加上keyvue3的watch监听可以进行终止监听
生命周期有了一定的区别Vue2————–vue3
beforeCreate->setup()开始创建组件之前,创建的是data和methodcreated->setup()beforeMount->onBeforeMount组件挂载到节点上之前执行的函数。mounted->onMounted组件挂载完成后执行的函数beforeUpdate->onBeforeUpdate组件更新之前执行的函数。updated->onUpdated组件更新完成之后执行的函数。beforeDestroy->onBeforeUnmount组件挂载到节点上之前执行的函数。destroyed->onUnmounted组件卸载之前执行的函数。activated->onActivated组件卸载完成后执行的函数deactivated->onDeactivatedvue与react的区别相同点:
不同点:
computed是vue中的计算属性,具有缓存性,当他的依赖于值,发生改变的时候才会重新调用methods是没有缓存的,只要调用,就会执行,一般结合事件来使用watch没有缓存性监听data中的属性属性值只要发生变化就会执行可以利用他的特性做一些异步的操作
created:dom渲染前调用,即通常初始化某些属性值mounted:在dom渲染后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作
Vue的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好(内部采用数组的方式存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)
开启严格模式,仅需在创建store的时候传入strict:true:conststore=newVuex.Store({//...strict:true})在严格模式下,无论何时发生了状态变更且不是由mutation函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。开发环境与发布环境不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
类似于插件,我们可以让构建工具来处理这种情况:
好处:
能够在vuex中集中管理共享的数据,易于开发和后期维护可以做状态管理、采用localstorage保存信息、数据一直存储在用户的客户端中存储在vuex中的数据都是响应式的,能够实时保持数据与页面的同步,能够高效地实现组件之间的数据共享,提高开发效率
vuex核心:
运行机制:Vuex提供数据(state),来驱动视图(这里指的是Vue组件),视图通过Dispatch派发Action,在Action中可以进一步做一些异步的操作(例如通过ajax请求后端的接口数据),然后通过Commit提交给Mutations,由Mutations去最终更改state。那么为什么要经过Mutations呢?这是因为我们要在Vue调试工具(Devtools)中记录数据的变化,这样可以通过插件去进行进一步的调试。所以说Mutations中只能是纯同步的操作,如果是有异步操作,那么就需要在Actions中进行处理。如果说没有异步操作,那么可以直接由组件进行Commit操作Mutations。
高级用法辅助函数(语法糖)mapState,mapActions,mapMutations,mapGetters
Import{mapActions,mapGetters,mapMutations,mapState}from‘vuex’computed(){…mapState([‘数据名字’])}
需要做vuex数据持久化一般使用本地存储的方案来保存数据可以自己设计存储方案也可以使用第三方插件推荐使用vuex-persist插件,它就是为Vuex持久化存储而生的一个插件。不需要你手动存取storage,而是直接将状态保存至cookie或者localStorage中
模块:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿。为了解决以上问题,Vuex允许我们将store分割成模块(module)。每个模块拥有自己的state、mutation、action、getter、甚至是嵌套子模块。方便管理
由于Vue在开发时对路由支持的不足,于是官方补充了vue-router插件。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,实际上就是组件的切换。路由就是SPA(单页应用)的路径管理器。再通俗的说,vue-router就是我们WebApp的链接路径管理系统。原理一般源码中,都会用到window.history和location.hash原理:通过改变浏览器地址URL,在不重新请求页面的情况下,更新页面视图,通过BOM中的location对象,其中对象中的location.hash储存的是路由的地址、可以赋值改变其URL的地址。而这会触发hashchange事件,而通过window.addEventListener监听hash值然后去匹配对应的路由、从而渲染页面的组件1.一种是#hash,在地址中加入#以欺骗浏览器,地址的改变是由于正在进行页内导航2.一种是h5的history,使用URL的Hash来模拟一个完整的URL
路由有两种模式hash和history模式默认是hashvue-router的实现原理(核心):更新视图但不重新请求页面。1、hash——即地址栏URL中的#符号,它的特点在于:hash虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。2、history——利用了HTML5Historyapi在浏览器中没有#有浏览器兼容问题3、history模式下,前端的URL必须和实际向后端发起请求的URL一致,否则返回404错误。
一:全局的守卫
无论访问哪一个路径,都会触发全局的钩子函数,位置是调用router的方法router/index.js
router.beforeEach全局前置守卫进入路由之前router.beforeResolve全局解析守卫,在beforeRouteEnter调用之后调用同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用router.afterEach全局后置钩子进入路由之后你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受next函数也不会改变导航本身:
二:组件级路由守卫放在要守卫的组件里,跟data和methods同级
//在组件内部进行配置,这里的函数用法也是和beforeEach一毛一样constFoo={template:`...`,beforeRouteEnter(to,from,next){//在渲染该组件的对应路由被confirm前调用//不!能!获取组件实例`this`//因为当守卫执行前,组件实例还没被创建},beforeRouteUpdate(to,from,next){//在当前路由改变,但是该组件被复用时调用//举例来说,对于一个带有动态参数的路径/foo/:id,在/foo/1和/foo/2之间跳转的时候,//由于会渲染同样的Foo组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。//可以访问组件实例`this`},beforeRouteLeave(to,from,next){//导航离开该组件的对应路由时调用//可以访问组件实例`this`}}三:单个路由规则独享的守卫写在路由配置中,只有访问到这个路径,才能触发钩子函数
beforeEnter:(to,from,next)=>{alert(“欢迎来到孙志豪的界面”)next()}
参数
重定向用哪个属性?redirect:”/路径”
1、
router.push:跳转,并向history栈中加一个记录,可以后退到上一个页面router.replace:跳转,不会向history栈中加一个记录,不可以后退到上一个页面router.go:传正数向前跳转,传负数向后跳转router.back返回到上一级页面
主要通过query和params来实现(1)query可以使用name和path而params只能使用name(2)使用params传参刷新后不会保存,而query传参刷新后可以保存(3)Params在地址栏中不会显示,query会显示(4)Params可以和动态路由一起使用,query不可以(5)to=”/goodsid=1001”this.然后在接收的页面通过$route.query.id来接收
route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。router是“路由实例对象”,包括了路由的跳转方法(push、go),钩子函数等。
有两种模式hash和history模式默认是hash1、hash——即地址栏URL中的#符号,它的特点在于:hash虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。2、history——利用了HTML5Historyapi在浏览器中没有#有浏览器兼容问题history模式下,前端的URL必须和实际向后端发起请求的URL一致,如地址后加上/items/id。后端如果缺少对/items/id的路由处理,将返回404错误。
使用原因:在单页应用中,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时原理:vue异步组件技术:异步加载,vue-router配置路由,使用vue的异步组件技术,实现按需加载。{path:‘/home’,component:()=>import(‘@/views/home/home.vue’)}//懒加载
hash模式location.hash的值实际就是URL中#后面的东西它的特点在于:hash虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。可以为hash的改变添加监听事件window.addEventListener(“hashchange”,funcRef,false);每一次改变hash(window.location.hash),都会在浏览器的访问历史中增加一个记录利用hash的以上特点,就可以来实现前端路由“更新视图但不重新请求页面”的功能了特点:兼容性好但是不美观history模式利用了HTML5HistoryInterface中新增的pushState()和replaceState()方法。这两个方法应用于浏览器的历史记录站,在当前已有的back、forward、go的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前URL改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。
修饰符.stop阻止事件冒泡.cpture设置事件捕获.self只有当事件作用在元素本身才会触发.prevent阻止默认事件,比如超链接跳转.once事件只能触发一次.native触发js原生的事件.number把文本框的内容转换为数字.trim去除文本框左右空格代码语言:javascript复制⑴v-bind:给元素绑定属性⑵v-on:给元素绑定事件⑶v-html:给元素绑定数据,且该指令可以解析html标签⑷v-text:给元素绑定数据,不解析标签⑸v-model:数据双向绑定⑹v-for:遍历数组⑺v-if:条件渲染指令,动态在DOM内添加或删除DOM元素⑻v-else:条件渲染指令,必须跟v-if成对使用⑼v-else-if:判断多层条件,必须跟v-if成对使用⑽v-cloak:解决插值闪烁问题⑾v-once:只渲染元素或组件一次⑿v-pre:跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度⒀v-show:条件渲染指令,将不符合条件的数据隐藏(display:none)v-for与v-if的优先级v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
“key值:用于管理可复用的元素。因为Vue会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做使Vue变得非常快,但是这样也不总是符合实际需求。2.2.0+的版本里,当在组件中使用v-for时,key是必须的。”key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确,更快的找到对应的vnode节点,更高效的对比虚拟DOM中每个节点是否是相同节点,相同就复用,不相同就删除旧的创建新的
key是使用index还是id啊
当Vue.js用v-for正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key的作用主要是为了高效的更新虚拟DOM。举例子:加入写一个带有复选框的列表选中第一个节点的复选框,点击删除,vue中是这样操作的,删除后新的数据这时会进行比较,第一个节点的标签一样,值不一样,就会复用原来位置的标签,不会做删除和创建,在第一个节点中是将复选框选中的,当我们看见好像是把第一个删除了,但是点击后去看复选框的时候还是选中在第一个,如果是直接将第一个节点删除了那么复选框就不会选中。
能够解决插值表达式闪烁问题,需要在style中设置样式[v-clock]{display:none}
v-if动态的创建或者销毁元素,为true的时候为显示,为false的时候不显示,要使用v-else必须和v-if紧挨着v-show是控制元素的显示或者隐藏,在我们的标签上会看到有display:block,nonev-if有更高的切换消耗,而v-show有更高的初始化渲染消耗,一般推荐频繁切换的时候使用v-show更好,当我们的判断分支比较多的时候,和首次渲染的时候使用v-if
出了vue自带的指定以外,我们如果需要对dom进行底层操作的时候这里就用到了自定义指令,分为一下全局:vue.directive:{“”,{}}局部:directives:{指令名:{钩子函数}}
参数:
el:指令所绑定的元素binding:一个对象包含一下,name:指令名,不包括v-前缀。value:指令的绑定值
补充:vue3的自定义指令钩子函数created-自定义指令所在组件,创建后beforeMount-就是Vue2.x中的bind,自定义指令绑定到DOM后调用.只调用一次,注意:只是加入进了DOM,但是渲染没有完成mounted-就是Vue2.x中的inserted,自定义指令所在DOM,插入到父DOM后调用,渲染已完成(最最重要)beforeUpdate-自定义指令所在DOM,更新之前调用updated-就是Vue2.x中的componentUpdatedbeforeUnmount-销毁前unmounted-销毁后自定义指令原理
1.在生成ast语法树时,遇到指令会给当前元素添加directives属性2.通过genDirectives生成指令代码3.在patch前将指令的钩子提取到cbs中,在patch过程中调用对应的钩子4.当执行指令对应钩子函数时,调用对应指令定义的方法
Mixin使我们能够为Vue组件编写可插拔和可重用的功能。mixin项目变得复杂的时候,多个组件间有重复的逻辑就会用到mixin如果希望在多个组件之间重用一组组件选项,例如生命周期hook、方法等,则可以将其编写为mixin,并在组件中简单的引用它。然后将mixin的内容合并到组件中。如果你要在mixin中定义生命周期hook,那么它在执行时将优化于组件自已的hook。
在下次DOM更新循环结束后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。使用场景是:可以在created钩子函数中拿到dom节点nextTick中的回调是在下次DOM更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。主要思路就是采用微任务优先的方式调用异步方法去执行nextTick包装的方法
官方解释:Vue.extend使用基础Vue构造器,创建一个“子类”。参数是一个包含组件选项的对象。其实就是一个子类构造器是Vue组件的核心api实现思路就是使用原型继承的方法返回了Vue的子类并且利用mergeOptions把传入组件的options和父类的options进行了合并基础用法
12种组件通信vue12种通信方式prop验证,和默认值props:会接收不同的数据类型,常用的数据类型的设置默认值的写法,Number,String,Boolean,Array,Function,Object所有的prop都使得其父子prop之间形成了一个单向下行绑定:父级prop的更新流动到子组件中,但是反过来则不行。这样防止子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。若果在子组件中直接修改prop传递的值,Vue会发出警告,
方法二:通过vuex实现(要了解)
具体实现:vuex是一个状态管理工具,主要解决大中型复杂项目的数据共享问题,主要包括state,actions,mutations,getters和modules5个要素,主要流程:组件通过dispatch到actions,actions是异步操作,再actions中通过commit到mutations,mutations再通过逻辑操作改变state,从而同步到组件,更新其数据状态
①项目使用keep-alive时,可搭配组件的name进行缓存过滤。②DOM做递归组件时需要调用自身name③vue-devtools调试工具里显示的组件名称是由vue中组件name决定的
在vue中的component中新建组件,定义好视图层,