自己整理的前端开发面试题

前端开发面试题

文章目录

JavaScript面试题

1-JavaScript数据类型

基本类型

ES5的5种:Null,undefined,Boolean,Number,String, ES6新增:Symbol表示独一无二的值 ES10新增:BigInt 表示任意大的整数

引用类型

对象object,数组array,日期date,函数function,正则RegExp

null和undefined的区别

相同点 在if语句中 null和undefined都会转换为false 两者使用相等运算符==也相等

不同点 undefined代表的含义是未定义 null代表的是空对象

ES10新增BIgInt表示任意大的整数

bigint数据类型的目的是比number数据类型的范围更大的整数值

数据类型的存储和堆栈内存

基本数据类型:直接存储在栈内存中,占据空间小,属于被频繁使用的数据

引用数据类型:同时存储在栈和堆内存中,占据空间大,大小不固定

栈和堆有什么区别

栈:是一种连续存储的数据结构,具有先进后出,后进先出的性质

堆:是一种非连续的树形存储数据结构,具有队列优先,先进先出

2-JavaScript数据类型判断,条件分支

if语句和逻辑运算

所有基本类型中的Boolean值是false的只有6个,分别是:0,null,undefined,false,NaN,空

逻辑运算符

&& 逻辑与 两边都为true,及条件都成立,才返回true,反之为false

|| 逻辑或 两边有一个为true,及条件有一个成立,就返回true,如果都为false返回false

! 逻辑非,取反布尔值

数据类型判断

typeof 对于基本数据类型的判断是没问题的,但是遇到引用数据类型是不起作用的

		console.log(typeof 2); //number
        console.log(typeof 'str'); //string
        console.log(typeof null); //object
        console.log(typeof undefined); //undefined
        console.log(typeof []);//object 
        console.log(typeof function fn() {
    
    }); //function

instanceof 只能正确判断引用数据类型 而不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型

      console.log([] instanceof Array)//true
      console.log(function () {} instanceof Function)//true
      console.log({} instanceof Object)//true
      console.log(new Date() instanceof Date)//true

constructor 似乎完全可以应对基本数据类型和引用数据类型 但如果声明了一个构造函数,并且把他的原型指向了 Array 的原型,所以这种情况下,constructor 也显得力不从心

  		 // 以下全为true
        console.log('str'.constructor === String) 
        console.log(true.constructor === Boolean)
        console.log([].constructor === Array)
        console.log(function() {}.constructor === Function)
        console.log({}.constructor === Object)
        console.log((2).constructor === Number)

JavaScript数据类型转换

在JavaScript中类型转换有三种情况

1,转为数字 调用number(),parseint(),paraseFloat()方法

number方法

 		Number('1') //1
        Number(true) //1
        Number(false)//0
        Number('12m')//NaN
        Number({})//NaN
        Number(null)//0
        Number(undefined)//NaN

parseint方法

        parseInt('2') //2
        parseInt('2',10) // 2
        parseInt('2',2)  // NaN
        parseInt('a123')  // NaN  如果第一个字符不是数字或者符号就返回NaN
        parseInt('123a')  // 123

parsefloat方法

        parseFloat('123a')//123
        parseFloat('123a.01')//123
        parseFloat('123.01')//123.01
        parseFloat('123.01.1')//123.01

2,隐式转换

隐式转换:当+两边有一个是字符串,另一个是其它类型时,会先把其它类型转换为字符串再进行字符串拼接,返回字符串

        let str = '123'
        let res = str - 1 //122
        str+1 // '1231'
        +str+1 // 124

3,转换为字符串

toString()方法 null和undefined不能调用此方法

 		Number(1).toString()//'1'
        [1,2].toString()//'1','2'
        true.toString()//'true'

string()方法都能转

  		String(123) //'123'
        String(true) //'true'
        String([]) //''
        String(null) //'null'
        String(undefined) //'undefined'
        String({}) //'[object Object]'

数据类型比较object.is, == 和===

===属于严格判断 直接判断两者类型是否相同

==表示值相等

Object.is()在===基础上特别处理了NaN,-0,+0,保证-0与+0不相等,但NaN与NaN相等

        '1' == 1 // true
        '1' === 1 // false
        NaN == NaN //false
        +0 == -0 //true
        +0 === -0 // true
        Object.is(+0,-0) //false
        Object.is(NaN,NaN) //true

typeof null的结果是什么 为什么

结果为object

在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits),以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:

        000: object   - 当前存储的数据指向一个对象。
          1: int      - 当前存储的数据是一个 31 位的有符号整数。
        010: double   - 当前存储的数据指向一个双精度的浮点数。
        100: string   - 当前存储的数据指向一个字符串。
        110: boolean  - 当前存储的数据是布尔值

        null的类型标签也是000,和Object的类型标签一样,所以会被判定为Object

3-JavaScript事件

事件是什么(冒泡和默认事件)

事件捕获,网景公司提出的事件流叫事件捕获流,由外往内,从事件发生的顶点开始,逐级往下查找,一直到目标元素

事件冒泡,IE提出的事件流叫做事件冒泡就是由内往外,从具体的目标节点元素触发,逐级向上传递,直到根节点。

事件委托,又名事件代理。事件委托就是利用事件冒泡,就是把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托也就没法实现了

阻止事件冒泡,event.stopPropagation() .stop修饰符。addEventListener(‘click’,函数名,true/false) 默认值为false(即 使用事件冒泡)true 事件捕获

好处:提高性能,减少了事件绑定,从而减少内存占用


<body>
    <div class="yeye">
        <div class="baba">
            <div class="erzi"></div>
        </div>
    </div>
    <script>
        var yeye = document.querySelector('.yeye')
        var baba = document.querySelector('.baba')
        var erzi = document.querySelector('.erzi')
        erzi.onclick = function(e) {
            alert('儿子')
                // e.stopPropagation() //第一种
                // e.cancelBubble = true//第二种
        }
        baba.onclick = function() {
            alert('爸爸')
        }
        yeye.onclick = function() {
            alert('爷爷')
        }
    </script>
</body>

阻止默认事件

 		 <a href="http://www.baidu.com">跳转百度</a>
  
        let a = document.querySelector('a')
        a.onclick = function(e) {
            e.preventDefault() //第一种
            e.returnValue = false //第二种
            return false//第三种
        }

JavaScript的作用域和作用域链

作用域,作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域

全局作用域就是Js中最外层的作用域,在哪里都可以访问

函数作用域是js通过函数创建的一个独立作用域,只能在函数内部访问,函数可以嵌套,所以作用域也可以嵌套

Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)

防抖节流

防抖:所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间(辅助理解为电梯进人,在你坐电梯时,当一直有人进电梯(连续触发),电梯门不会关闭,在一定时间间隔内没有人进入(停止连续触发)才会关闭)

节流:所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。两种方式可以实现,分别是时间戳版和定时器版。

函数防抖是:当你频繁触发后,n秒内只执行一次。

函数节流是:在固定的时间内触发事件,每隔n秒触发一次。

鼠标事件 mouseenter和mouseover区别

mouseenter: 鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发。

mouseover: 鼠标进入被绑定事件监听元素节点时触发一次,如果目标元素包含子元素,鼠标移出子元素到目标元素上也会触发。

mouseenter 不支持事件冒泡 mouseover 会冒泡

4-JavaScript应用数据类型Object

object的方法

Object.is() 是一种判断两个值是否相同的方法。
语法:Object.is(value1, value2);
参数:value1:要比较的第一个值。value2:要比较的第二个值。
返回值:一个布尔表达式,指示两个参数是否具有相同的值。
 
Object.assign() 方法用于将所有可枚举的自身属性从一个或多个源对象复制到目标对象。
语法:Object.assign(target, ...sources)
参数:target:目标对象——应用源属性的对象,修改后返回。sources:源对象——包含你要应用的属性的对象。
返回值:修改后的目标对象。
 
 
Object.entries() ES8的Object.entries是把对象转成键值对数组, [key, value] 对的数组。
语法:Object.entries(obj)
参数:obj:要返回其自己的可枚举字符串键属性 [key, value] 对的对象。返回值:给定对象自己的可枚举字符串键属性 [key, value] 对的数组。
Object.fromEntries则相反,是把键值对数组转为对象
 
Object.values() 方法返回给定对象自己的可枚举属性值的数组,其顺序与 for...in 循环提供的顺序相同。
语法:Object.values(obj)
参数:obj:要返回其可枚举自身属性值的对象。返回值:包含给定对象自己的可枚举属性值的数组。
 
Object.prototype.hasOwnProperty()
hasOwnProperty() 方法返回一个布尔值,指示对象是否具有指定的属性作为它自己的属性。
如果指定的属性是对象的直接属性,则该方法返回 true — 即使值为 null 或未定义。如果该属性是继承的或根本没有声明,则返回 false。
语法:hasOwnProperty(prop)
参数:prop:要测试的属性的字符串名称或符号。
返回值:如果对象将指定的属性作为自己的属性,则返回true;否则为false。
 
Object.keys()
Object.keys() 方法用于返回给定对象自己的可枚举属性名称的数组,以与普通循环相同的顺序迭代。
语法:Object.keys(obj)
参数:obj:要返回可枚举自身属性的对象。
返回值:表示给定对象的所有可枚举属性的字符串数组。
 
Object.prototype.toString()
toString() 方法返回一个表示对象的字符串。当对象将被表示为文本值或以期望字符串的方式引用对象时,将自动调用此方法 id。默认情况下,toString() 方法由从 Object 继承的每个对象继承。
语法:toString()
返回值:表示对象的字符串。
 
Object.freeze()
Object.freeze() 方法冻结一个对象,这意味着它不能再被更改。冻结对象可防止向其添加新属性,防止删除现有属性,防止更改现有属性的可枚举性、可配置性或可写性,并防止更改现有属性的值。它还可以防止其原型被更改。
语法:Object.freeze(obj)
参数:obj:要冻结的对象。返回值:传递给函数的对象。
 
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 (请打开浏览器控制台以查看运行结果。)
语法:const me = Object.create(person);
参数:
proto:新创建对象的原型对象。
propertiesObject
可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。
返回值
一个新对象,带着指定的原型对象和属性。

深拷贝和浅拷贝

深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝

1-浅拷贝

  • 将原对象或原数组的引用直接复制给新的对象或新的数组,新对象只是对原对象的一个引用,而不是复制对象本身,新旧对象共享一个内存
  • 如果属性是基本数据类型 拷贝的就是数据类型的值,如果属性是引用类型,拷贝的是内存地址

2-深拷贝

  • 创建一个新的对象或者数组,将原对象的各项属性的值拷贝过来,
  • 深拷贝就是把对象,从内存中完整的拷贝出来,从堆内存开辟一块新区域,用来存新对象,并且修改新对象对原对象没有影响

3-赋值

  • 当我们把一个对象赋值给一个新的变量时 赋的是该对象在栈内存的内存地址,而不是堆内存的数据 也就是两个对象
	浅拷贝的实现方式:
   1、object.assign()
   2、lodash 里面的 _.clone 
   3、...扩展运算符
   4、 Array.prototype.concat 
   5、 Array.prototype.clice
 
    深拷贝的实现方式
    1、JSON.parse(JSON.stringify())
    2、递归操作
    3、cloneDeep
    4、Jquery.extend()   

5-JavaScript数组

数组方法

  1. sort():排序,如果下面参数的正反,控制,升序,降序,返回从新排序的原数组,有一个可选参数(可指定排序条件),没写默认按各个字符的Unicode位点进行排序

  2. splice():向数组指定的index处插入,返回的是被删除的数组元素的集合,第一个参数表示开始截取的索引位,第二个参数表示截取的长度,返回截取的 数组,原数组改变;三个或者更多参数,第三个及以后的参数表示要从截取位插入的值,会改变原数组

    		let arr = [1, 2, 3, 4, 5]
         	//参数2为从下标为2开始,第二个参数为截取几个   
            let cutArr = arr.splice(2, 2, '我是被插入的数据')
            console.log(arr)//[1, 2, '我是被插入的数据', 5]
            console.log(cutArr)//[3, 4]
    
  3. pop():从数组尾部删除一个数据,返回被删掉的元素,会改变原数组

  4. push():从数组尾部添加数据,返回新的数组长度,会改变原数组

  5. shift():从数组头部删除一个元素,返回被删掉的元素,会改变原数组

  6. unshift():从数组头部添加数据,返回新的数组长度,会改变原数组

  7. reverse():反转数组,不会改变原数组

  8. concat():数组合并

  9. slice():截取数组,返回新截取的数组,不会改变原数组

     		 let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
            let newArr = arr.slice(1, 3) //[2,3] 从第一位开始,但不包括第一位,从第三位结束
            console.log(newArr)
    
  10. join():讲数组分割为字符串,返回值为字符串和添加的内容,只能分割一层,不会改变原数组

    		let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
            let res = arr.join('-')
            console.log(res)//1-2-3-4-5-6-7-8-9
    
  11. filter():对数组中的每一运行给定的函数,会返回满足该函数的项组成的数组。过滤

    		let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
            let res = arr.filter(i => i > 4)
            console.log(res) //[5, 6, 7, 8, 9]
    
  12. every():当数组中每一个元素在callback上被返回true时就返回true

    		let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
            let res = arr.every(i => i > 0)
            console.log(res) //true
    
  13. some():当数组中有一个元素在callback上被返回true时就返回true。

     		let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
            let res = arr.some(i => i > 3)
            console.log(res) //true
    
  14. reduce( ):回调函数中有4个参数。prev(之前计算过的值),next(之前计算过的下一个的值),index,arr。把数组列表计算成一个

  15. isArray():判断是否为数组

  16. IndexOf 方法可返回某个指定的字符串值在字符串中首次出现的位置,如果没有找到匹配的字符串则返回 -1

       		let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
            let res = arr.indexOf(4)
            let res1 = arr.indexOf(21)
            console.log(res) //3
            console.log(res1) //-1
    
  17. lastIndexOf 它是从最后一个值向前查找的 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1

  18. Array.of(),填充单个值

  19. find():查找这一组数 符合条件的第一个数 给他返回出来

  20. forEach():遍历原数组

  21. map():

6-JavaScript字符串

字符串方法

  1. chartAt( ):返回在指定位置的字符;

  2. concat( ):返回新的字符串**,将一个或多个字符串与原字符串连接合并

  3. indexOf( ):检索字符串,返回第一次出现的索引,没有出现则为-1

  4. lastIndexOf(searchValue[ fromIndex]) 返回从字符串尾部开始第一次出现的索引,没有则-1,fromIndex的值相对于从尾部开始的索引

  5. split( ):返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符

    		let str = '李-育-狗-是-个-狗'
            let res = str.split('-', 6)
            console.log(res)//[ '李', '育', '狗', '是', '个', '狗' ]
    
  6. substr( ):从起始索引号提取字符串中指定数目的字符

    		let str = 'hello'
            console.log(str.substr(1, 3))//ell
    
  7. substring( ):提取字符串中两个指定的索引号之间的字符

    		let str = 'hello'
            console.log(str.substring(1, 3)) //el
    
  8. toLowerCase( ):字符串转小写;

  9. toUpperCase( ):字符串转大写

  10. valueOf( ):返回某个字符串对象的原始值

  11. trim( ):删除字符串两边的空格

  12. trimeState 取出开始的空格

  13. trimeEnd 去除末尾空格

  14. includes(searchString[, position])返回boolean,判断一个字符串是否包含在另一个字符串中,从postition索引开始搜寻,默认0

     		let str = '张三李四王五'
            alert(str.includes('李四'))//true
    
  15. slice( ):提取字符串片段,并在新的字符串中返回被提取的部分

            let str = '张三李四王五'
            console.log(str.slice(2, 6)) //李四王五,从第二位截到第六位 但是不包含第二位
    
  16. search(regexp)返回首次匹配到的索引,没有则-1,执行正则表达式和 String 对象之间的一个搜索匹配

  17. toString()返回一个表示调用对象的字符串,该方法返回指定对象的字符串形式

  18. replace() 把指定的字符串替换成为别的字符

7-JavaScript函数

函数声明的几种方式

        函数声明
        function 函数名(参数1,参数2,...){   //要执行的语句 } 
        函数表达式
        var func2=function(b){}//函数表达式
        var func3=function func4(c){}//命名式函数表达式
        var func5=(function(n1,n2){})();//立即执行的函数表达式
        return function(){ };//作为返回值的函数表达式
        Function构造器
        var 变量名 = new Function("参数1","参数2",...,"参数n","函数体");  
        立即执行函数
        var func5=(function(n1,n2){})();//立即执行的函数表达式 ()()

函数声明与函数表达式的区别

函数声明会将那个函数提升到最前面(即使你写代码的时候在代码块最后才写这个函数),成为全局函数。

函数声明要指定函数名,而函数表达式不用,可以用作匿名函数。

this指向问题

全局环境下this指向window

普通函数调用中的this指向window,严格模式this指向undefined,自执行函数指向window 定时器指向window

在对象里调用this 指向调用函数的那个对象

构造函数配合new使用 指向实例化的对象

箭头函数没有自己的this 会继承外层的第一个普通函数的this

call,apply,bind

都是来修改this指向的,三者第一个参数都是this要指向的对象

call和apply的区别是 call的参数是直接放进去的一个个参数列表 而apply是一个数组

bind除了返回的是一个函数以外 他的参数和call一样,但是它必须调用才会执行,bind返回的函数不可以作为构造函数

闭包

闭包的概念,能够读取其他函数内的变量的函数

闭包的缺点:由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

8-JavaScript作用域,机制

垃圾回收机制和内存机制

垃圾回收:垃圾回收机制也就是自动内存管理机制,垃圾收集器会定期的找出那些不在继续使用的变量,然后释放内存

内存泄漏:如果 那些不再使用的变量,它们所占用的内存 不去清除的话就会造成内存泄漏,如闭包,times计时器泄露

作用域

作用域就是一个变量可以使用的范围,主要分为全局作用域和函数作用域

全局作用域就是Js中最外层的作用域

函数作用域是js通过函数创建的一个独立作用域,函数可以嵌套,所以作用域也可以嵌套

Es6中新增了块级作用域(由大括号包裹,比如:if(){},for(){}等)

自由变量

当前作用域外的变量都是自由变量,一个变量在当前作用域没有定义,但是被使用了,就会向上级作用域,一层一层依次查找,直至找到为止,如果全局作用域都没有找到这个变量就会报错。这个自由变量查找的过程就是作用域链

浅谈JavaScript的运行机制

单线程:JavaScript语言的一大特点就是单线程,即同一时间只能做一件事情

JS事件循环和执行顺序

js代码执行会有很多任务,这些任务分为两类

  • 同步任务
  • 异步任务
  • 任务可以更加细分为微任务和宏任务,js引擎会优先执行微任务

​ 微任务包括promise的回调,node中的process.nextTick

​ 宏任务包括script脚本执行,setTimeout,setInterval,等

执行顺序

  1. 首先js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。

  2. 在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务

  3. 当同步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。

  4. 任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。

  5. 当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。

8-BOM浏览器对象模型

JavaScript操作BOM

window : alert() , prompt() , confirm() , setInterval() , clearInterval() , setTimeout() , clearTimeout() ;

window.location.href = ‘你所要跳转到的页面’; 2、window.open('你所要跳转到的页面’); 3、window.history.back(-1):返回上一页 4、window.history.go(-1/1):返回上一页或下一页五、 5、history.go(“baidu.com”);

Math对象的成员

  • Math.PI 圆周率

  • Math.floor() 向下取整

  • Math.ceil() 向上取整

  • Math.round() 四舍五入版 就近取整

  • Math.abs() 绝对值

  • Math.max()/Math.min() 求最大和最小值

            console.log(Math.max(2, 3, 5)) //5
    
  • Math.random() 获取范围在[0,1)内的随机值

setTimeout与setInterval区别与机制

setTimeout()和setInterval()经常被用来处理延时和定时任务。

setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式,执行一次

setInterval()则可以在每隔指定的毫秒数循环调用函数或表达式,直到clearInterval把它清除

cookies,sessionStorage和localStorage的区别

cookie:一个大小不超过4K的小型文本数据,一般由服务器生成,可以设置失效时间;若没有设置时间,关闭浏览器cookie失效,若设置了 时间,cookie就会存放在硬盘里,过期才失效,每次http请求,header都携带cookie

localStorage:5M或者更大,永久有效,窗口或者浏览器关闭也会一直保存,除非手动永久清除或者js代码清除,因此用作持久数据,不参与和服务器的通信

sessionStorage关闭页面或浏览器后被清除。存 放数据大小为一般为 5MB,而且它仅在客户端(即浏览器)中保存,不参与和服务器的通信。

9-ES6部分面试题

ES6新增特性

  1. 新增块级作用域(let const)

  2. 提供了定义类的语法糖(class)

  3. 新增了一种基本数据类型(Symbol)

  4. 新增变量的解构赋值

  5. 新增箭头函数

  6. 新增一些API,如 isArray / from / of 方法;数组实例新增了entries(),keys() 和 values() 等方法

  7. 对象和数组新增扩展运算符

  8. 新增模块化

  9. 新增set和map数据结构

  10. 新增proxy构造函数用来生成proxy实例

  11. 新增生成器(Generator)和遍历器(iterator)

require与import的区别与使用方法

import是ES6的语法标准也是用来加载模块的 import函数可以读取并执行一个JavaScript文件,然后返回改文件的export命令输出的代码,export与export default均可导出常量,函数,文件,模块,export可以有多个,export default只能有一个

require定义模块:module变量代表当前模块,它的exports属性是对外的接口,通过exports可以将模块从模块中导出,其他文件加载该模块实际上读取modules.exports变量,他们可以是量、函数、对象等。在node中如果用exports进行导出的话系统会系统帮您转成module.exports的,只是导出需要定义导出名

两者的区别

require是commonJS规范的模块化语法,import是ECMAScript6规范的模块化语法

require是运行时加载,import是编译时加载

require可以写在代码的任意位置,impot只能写在文件的最顶端且不可在条件语句或函数作用域中使用

require通过modules.exports导出的值不能改变,import通过export导出的值可以改变

require通过modules.exports导出的是exports对象,impot通过export导出的是指定输出的代码

require运行时才引入模块的属性,所有性能较低,import编译时引入的模块属性,性能稍高

箭头函数

  1. 箭头函数是匿名函数,不能作为构造函数,不能使用new
  2. 箭头函数不绑定arguments,取而代之用rest参数解决
  3. this指向不同,箭头函数的this在定义的时候继承自外层的第一个普通函数的this
  4. 箭头函数没有prototype,所有箭头函数没有this
  5. 箭头函数不能当做Generator函数,不能使用yield关键字
  6. 箭头函数把function省略,使用=>,也可以把return省掉
  7. 箭头函数不能通过call(),apply(),bind()改变this指向

简述var let const 的区别,以及使用场景

var

var 声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined

一个变量可以被多次声明,后者会覆盖前者

在函数内使用var声明变量 该变量是局部的作用域,只在函数内 在函数外为全局变量

let

不存在变量提升

let在块级作用域有效,作用域为花括号

let不允许在相同作用域重复声明

const

声明一个只读的常量,声明后,值不能改变,但对于对象和数据这种 引用类型,内存地址不能修改,可以修改里面的值。

其他同let一样

注意

能用const的情况下尽量使用const,大多数情况使用let,避免使用var。 const > let > var const声明的好处,一让阅读代码的人知道该变量不可修改,二是防止在修改代码的过程中无意中修改了该变量导致报错,减少bug的产生

map和forEach的区别

相同点,都是循环遍历数组中的每一项,都支持3个参数,(item,index,arr)

forEach对于空数组是不会调用回调函数的

map方法也不会对空数组进行检测

不同点

map方法返回的一个新的数组,数组中的元素为原始数组处理过后的值,不会改变原数组

forEach方法用于调用数组的每个元素 将元素传给回调函数,没有return 返回值是undefined

promise的解释

  1. promise是一种异步编程的解决方案,主要用于异步计算,支持链式调用,可以解决回调地狱的问题,自己身上有,all,reject,resolve,race等方法,原型上有then,catch方法
  2. 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
  3. promise有三种状态,pending(待定)初始状态,fulfilled(实现)操作成功,rejected(被否定)操作失败
  4. promise对象状态改变,从pending变为fulfilled,或者从pending变为rejected,只有这两种状态 状态凝固就不会发生变化了
  5. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部,但是写了then 和 catch ,会被then的第二个参数 或 catch所捕获

promise的then为什么可以支持链式调用

promise 的then会返回一个新的 promise 对象,能保证 then 方 可以进行链式调用

async与await的原理

  • async与await是一种同步写法,但是是异步操作,两个必须配合一起使用

  • 函数前面的async关键字表明内部有异步操作,调用该函数时,会返回一个promise对象

  • await是运算符,用于组成表达式,

  • await 能够获取promise执行的结果 await必须和async一起使用才行,async配合await使用是一个阻塞的异步方法

  • 如果await后面不是Promise对象, 就直接返回对应的值,只能在async函数中出现, 普通函数直接使用会报错

  • await语句后的Promise对象变成reject状态时,那么整个async函数会中断,后面的程序不会继续执行

解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值

常用的几种方式有

  1. 默认值
  2. 交换变量
  3. 将剩余数组赋给一个变量

element-Ui按需引入就使用了解构赋值

for…in 迭代和 for…of 有什么区别

  1. 推荐在循环对象属性的时候使用for…in,在遍历数组的时候使用for…of

            let arr = [1, 2, 3, 4, 5]
            let obj = {
                name: '张三',
                age: 20
            }
            for (k of arr) {
                console.log(k) //1,2,3,4,5
            }
    		for (i in arr) {
                console.log(i) //0,1,2,3,4,
            }
            for (v in obj) {
                console.log(v) //name,age
            }
    
  2. for in 便利的是数组的索引 而for of遍历的是数组元素值

  3. for…of 不能循环普通的对象,需要通过和 Object.keys()搭配使用

  4. for…in 便利顺序以数字为先 无法便利 symbol 属性 可以便利到公有中可枚举的

  5. 从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错

generator

  • Generator 生成器 也是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同 function *(){}

  • Generator 函数是一个状态机,封装了多个内部状态,除了状态机,还是一个遍历器对象生成函数。

  • Generator 是分段执行的, yield (又得)可暂停,next方法可启动。每次返回的是yield后的表达式结果,这使得Generator函数非常适合将异步任务同步化

  • Generator 并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator`接口…)

  • Generator函数返回Iterator对象,因此我们还可以通过for…of进行遍历,原生对象没有遍历接口,通过Generator函数为它加上这个接口,就能使用for…of进行遍历了

promise、Generator、async/await进行比较

  • promise和async/await都是用来处理异步操作的
  • generator并不是为异步而设计出来的,他还有其他功能(对象迭代,控制输出,部署接口)
  • promise相较于generator,async/await更为复杂,且可读性也稍差
  • async实质上是generator的语法糖
  • async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案

构造函数生成实例的执行过程:使用面向对象编程时,new关键字做了什么?

  1. 新建了一个Object对象
  2. 修改构造函数this的指向,是其指向新建的Object对象,并且执行构造函数
  3. 为Object对象添加了一个proto属性,是其指向构造函数的prototype属性
  4. 将这个Object对象返回出去

set和map数据结构有哪些常用的属性和方法?

set数据的特点是数据是唯一的

const set1 = new Set()
 
增加元素 使用 add
set2.add(4)
 
是否含有某个元素 使用 has
console.log(set2.has(2)) 
     
查看长度 使用 size
console.log(set2.size) 
 
删除元素 使用 delete
set2.delete(2)
 
size: 返回Set实例的成员总数。
add(value):添加某个值,返回 Set 结构本身。
delete(value):删除某个值。
clear():清除所有成员,没有返回值。

Set的不重复性

传入的数组中有重复项,会自动去重
const set2 = new Set([1, 2, '123', 3, 3, '123'])
 
Set`的不重复性中,要注意`引用数据类型和NaN
两个对象都是不用的指针,所以没法去重
const set1 = new Set([1, {name: '孙志豪'}, 2, {name: '孙志豪'}])
 
如果是两个对象是同一指针,则能去重
const obj = {name: '我们一样'}
const set2 = new Set([1, obj, 2, obj])
 
NaN !== NaN,NaN是自身不等于自身的,但是在Set中他还是会被去重
const set = new Set([1, NaN, 1, NaN])

map数据结构

Map对比object最大的好处就是,key不受类型限制

 
定义map
const map1 = new Map()
 
新增键值对 使用 set(key, value)
map1.set(true, 1)
 
判断map是否含有某个key 使用 has(key)
console.log(map1.has('哈哈')) 
 
获取map中某个key对应的value
console.log(map1.get(true)) 
 
删除map中某个键值对 使用 delete(key)
map1.delete('哈哈')
 
 
定义map,也可传入键值对数组集合
const map2 = new Map([[true, 1], [1, 2], ['哈哈', '嘻嘻嘻']])
console.log(map2) // Map(3) { true => 1, 1 => 2, '哈哈' => '嘻嘻嘻' }

proxy的理解

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

s6中新的数据类型symbol

symbol 是es6 加入的,是一个基本数据类型,它代表的是一个独一无二的值,SYMBOL 值是由 SYMBOL函数生成

symbol 不能用来四则运算,否则会报错,只能用显示的方式转为字符串

如果拿 symbol 对比的话 就是会返回 false

symbol 他是一个原始类型的值就,不可以使用 new 关键字,symbol不是对象 没有迭代器的接口 不能去添加属性值,他是类似于字符串的一种类型

symbol 参数里的 a 表示一种修饰符 对当前创建的 symbol 的一种修饰,作为区分 ,否则会混淆

iterator == iteration (遍历器的概念)

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作

Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。

其实iteration == iterator 有三个作用:

  1. 为各种数据结构,提供一个统一的、简便的访问接口;

  2. 使得数据结构的成员能够按某种次序排列;

  3. 主要供for…of消费

Object.assign

object.assign可以实现对象的合并,它的语法是这样的: Object.assign(target, ...sources)

Object.assign会将source里面的可枚举属性复制到target。如果和target的已有属性重名,则会覆盖。同时后续的source会覆盖前面的source的同名属性

Object.assign复制的是属性值,如果属性值是一个引用类型,那么复制的其实是引用地址,就会存在引用共享的问题

Object.assign可以实现浅拷贝

Array.from()

Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。

那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有length属性的对象

        let arrayLike = {
            0: 'tom', 
            1: '65',
            2: '男',
            3: ['jane','john','Mary'],
            'length': 4
        }
        let arr = Array.from(arrayLike)
        console.log(arr) // ['tom','65','男',['jane','john','Mary']]

如果将上面代码中 length 属性去掉呢?实践证明,答案会是一个长度为0的空数组

这里将代码再改一下,就是具有 length 属性,但是对象的属性名不再是数字类型的,而是其他字符串型的,代码如下

        let arrayLike = {
            'name': 'tom', 
            'age': '65',
            'sex': '男',
            'friends': ['jane','john','Mary'],
            length: 4
        }
        let arr = Array.from(arrayLike)
        console.log(arr)  // [ undefined, undefined, undefined, undefined ]

会发现结果是长度为4,元素均为 undefined 的数组

由此可见,要将一个类数组对象转换为一个真正的数组,必须具备以下条件:

1、该类数组对象必须具有 length 属性,用于指定数组的长度。如果没有 length 属性,那么转换后的数组是一个空数组。

2、该类数组对象的属性名必须为数值型或字符串型的数字

谈谈你对模块化开发的理解

一个模块是实现一个特定功能的一组方法。在最开始的时候,js 只实现一些简单的功能,所以并没有模块的概念 ,但随着程序越来越复杂,代码的模块化开发变得越来越重要。

由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污 染,并且模块间没有联系

后面提出了对象写法,通过将函数作为一个对象的方法来实现**,**这样解决了直接使用函数作为模块的一些缺点,但是这种办法会暴露所 有的所有的模块成员,外部代码可以修改内部属性的值。

js 的几种模块规范

js 中现在比较成熟的有四种模块加载方案:

  • 第一种是 CommonJS 方案,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。
  • 第二种是 AMD 方案,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。
  • 第三种是 CMD 方案,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。
  • 第四种方案是 ES6 提出的方案,使用 import 和 export 的形式来导入导出模块。

VUE面试题

1-核心原理部分

MVVM

MVVM就是Model-View-ViewModel的缩写,MVVM将视图和业务逻辑分开

View:视图层,Model:数据模型,ViewModel是两者建立通信的桥梁

在 MVVM 框架下,View 和 Model 之间没有直接的联系,而是通过 ViewModel 进行交互。View 和 ViewModel 之间以及 Model 和 ViewModel 之间的交互都是双向的,因此 view 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反映到 View 上。可以说它们两者是实时更新的,互相影响。 ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而 View 和 Model 之间的同步工作完全是自动的,因此开发者只需要关注业务逻辑,不需要手动操作 DOM,也不需要关注数据状态的同步问题,这些都由 MVVM 统一管理

VUE底层实现原理

vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调,vue是一个典型的MVVM框架,

Observer(数据监听器):observer的核心是通过object.defineProperty来监听数据的的变化,这个函数内部可以定义setter和getter,每当数据发生变化都会触发setter,这时候observer就会通知订阅者,订阅者就是watcher

Compile(指令解析器):Compile主要做的事情就是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新视图

Watcher(订阅者):watcher订阅者作为Observer和compile之间通信的桥梁,主要做的事情是

  1. 在自身实例化时往属性订阅器里面添加自己
  2. 自身必须有一个update()方法
  3. 待属性变动dep.notice()通知时,能调用自身的update方法,并触发Compile中绑定的回调

VUE模板编译原理

vue中的template无法被浏览器解析并渲染,因为不属于浏览器的标准,不是正确的HTML语法,所以需要将template转化为一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染对应的HTML元素,就可以让视图跑起来了。这一个转化过程,就成为了模板编译

VUE的编译过程就是将template转化为render函数的过程,分为以下三步

第一步:将模板字符串转换为element ASTS(解析器)

第二步:对AST进行静态节点标记,主要是用来做虚拟DOM的渲染优化(优化器)

第三步:是使用element ASTS生成render函数代码字符串(代码生成器)

VUE虚拟DOM,diff算法

虚拟DOM,其实就是用对象的方式取代真实的DOM操作,把真实的DOM操作放在内存当中,在内存中的对象里做虚拟操作,当页面打开时浏览器会解析HTML元素,构建一个DOM树,两棵DOM树进行比较,根据diff算法比较两棵DOM树的不同,只渲染一次不同的地方

(个人理解)虚拟DOM并不是真是DOM,是根据模板生成的js对象(使用CreteElement方法),根据这个js对象再去生成真实的DOM,对复杂的文档DOM解构,提供一种方便的工具,进行最小化的DOM操作,是可以快速的渲染和高效的更新元素,提升浏览器性能,

我们在渲染页面的时候 会对新的虚拟dom和旧的虚拟dom进行对比 只渲染不同的地方,而不再是像之前只要发生变化,全部的真实dom都要重新渲染,所以提高了渲染的效率

缺点首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢

diff算法

diff 算法是一种通过同层的树节点进行比较的高效算法,比较方式:diff整体策略为:深度优先,同层比较

diff算法 当data发生改变 会根据新的数据生成一个新的虚拟dom ,新的虚拟dom和旧的虚拟dom进行对比,这个对比的过程就是diff算法,会找到不同地方,只去渲染不同的地方,总的来说就是减少DOM,重绘和回流。

为什么要用虚拟DOM来描述真实DOM

创建真实DOM成本比较高,如果用js对象来描述一个dom节点,成本比较低,另外我们在频繁操作dom是一种较大的开销,所以建议虚拟dom来描述真实dom

响应式原理

什么是响应式,“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。

Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。


	Object.defineProperty怎么用, 三个参数?,有什么作用啊?
    Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回		此对象。
     Object.defineProperty(obj, prop, {})
     obj:需要定义属性的对象
     prop:需要定义的属性
     {}:要定义或修改的属性描述符。
     value: "18",         // 设置默认值得
     enumerable: true,    //这一句控制属性可以枚举 enumerable 改为true 就可以参与遍历了,默认值false
     writable: true,      // 控制属性可以被修改   默认值false
     configurable: true,  // 控制属性可以被删除   默认值false
      get // 当有人读取 prop 的时候  get函数就会调用,并且返回就是 sss 的值
      set // 当有人修改 prop 的时候  set函数就会调用, 有个参数这个参数就是修改后的值
      
      
Object.defineProperty 能定义symbol类型吗? 
       在ES6中,由于 Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而Object.defineProperty 是定        义key为Symbol的属性的方法之一。
    
vue2和vue3的响应式原理都有什么区别呢?
vue2 用的是 Object.defindProperty 但是vue3用的是Proxy 
Object.defindProperty虽然能够实现双向绑定了,但是还是有缺点,只能对对象的属性进行数据劫持,所以会深度遍历整个对象,不管层级有多深,只要数组中嵌套有对象,就能监听到对象的数据变化无法监听到数组的变化,Proxy就没有这个问题,可以监听整个对象的数据变化,所以用vue3.0会用Proxy代替definedProperty。
上面就是一个典型的例子,当我们点击按钮想要根据数组 arr 的下标改变其元素的时候,你会发现 data 中的数据改变了,但是页面中的数据并没有改变。
         我会用  this.$set( target, key, value ) 来解决
       参数:
         {Object | Array} target
         {string | number} propertyName/index
         {any} value 
         第一参数时指定要修改的数据 (target)
         第二个参数就是你要设置数据的下标或者是属性名
         第三个参数就是现在要修改的数据 (重新赋的值)
改变/添加 对象属性的时候:this.$set(data 实例,"属性名(添加的属性名)","属性值(添加的属性值)")
改变/添加 数组属性的时候:this.\$set(data 实例,数组下标,"改变后的元素(添加的元素)")
​
原因 : vue在创建实例的时候把data深度遍历所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。让 Vue 追踪依赖,在属性被访问和修改时通知变化。所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
​
为什么要用  this.$set  呢? this.$set是干什么的?
      当你发现你给对象加了一个属性,在控制台能打印出来,但是却没有更新到视图上时,也许这个时候就需要用到this.$set()这个方法了,简单来说this.$set的功能就是解决这个问题的啦。官方解释:向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty = 'hi'),你会发现vue官网是vue.set,vue.set的用法
​
​
       
那 Vue.set 和 this.$set 有什么区别 ?
    Vue.set( ) 是将 set 函数绑定在 Vue 构造函数上,this.$set() 是将 set 函数绑定在 Vue原型上

vue双向数据绑定原理

是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调从而达到数据和视图同步。

主要分为四部分

  1. observer 主要是负责对Vue数据进行递归便利,使其数据拥有get和set方法,当有数据给某个对象值赋值,就触发 setter 就监听到数据的变化了。( 如有变动可拿到最新值并通知订阅者 )

  2. compile 指令解析器负责绑定数据和指令解析。 将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数。一旦数据有变动,收到通知,更新视图

  3. 订阅者 watcher : Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是 负责数据监听,当数据发生改变,能调用自身的update()方法,并触发Compile中绑定的更新函数

  4. 实现一个订阅器 dep: 采用发布者订阅者模式,用来收集订阅者的 watcher,对监听器 observer 和订阅者 watcher 进行统一管理

vue3的Proxy 相比于 vue2的defineProperty 的优势

在vue3 中
Vue3是通过Object.define.proxy 对对象进行代理,从而实现数据劫持。使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法
​
Vue3.0 摒弃了 Object.defineProperty,改为基于 Proxy 的观察者机制探索。
首先说一下 Object.defineProperty 的缺点:
① Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。 this.$set()解决
② Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue2.X 里,是通过递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象才是更好的选择。


而要取代它的 Proxy 有以下两个优点
可以劫持整个对象,并返回一个新对象。有多种劫持操作(13 种)
补充:
Proxy 用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 mdn
Proxy 是 ES6 新增的一个属性,翻译过来的意思就是代理,用在这里表示由它来“代理”某些操作。Proxy 让我们能够以简洁易懂的方式控制外部对象的访问,其功能非常类似于设计模式中的代理模式。


​
1、vue 中数组中的某个对象的属性发生变化,视图不更新如何解决?
 Object.defineProperty 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实施响应。 this.$set()解决
 问题原因:因为 vue 的检查机制在进行视图更新时无法监测 数组中的对象的某个属性值的变化。解决方案如下
方案一:利用  this.set(this.obj,key,val)
例:this.set(this.obj,‘k1’,‘v1’)
方案二:就利用 Object.assign({},this.obj)创建新对象  
如果是数组就 Object.assign([],this.obj)
如果是对象就 Object.assign({},this.obj)。

vue.js核心

灵活的组件应用,高效的数据绑定

渐进式框架的理解,vue数据驱动的理解

渐进式代表的含义是:主张最少——它是一个轻量级框架,只做了自己该做的事,没有做不该做的事

每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式。

这里的vue数据驱动的是视图,也就是DOM元素,指的是让DOM的内容随着数据的改变而改变框架的理解

Vue的SSR是什么?有什么好处?

SSR全称Server Side Render

有利于SEO:由于是在服务端,将数据填充进HTML之后再推送到浏览器,所以有利于SEO的爬取

首屏渲染快

SSR的缺点:

开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子;

当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境;

更多的服务端负载。

vue2.0和vue3.0的区别

1.性能提升

更小巧,更快速;支持摇树优化。支持 Fragments (支持多个根节点)和跨组件渲染;支持自定义渲染器。

2.API 变动

Vue2使用 选项类型API(Options API) 对比Vue3 合成型API(Composition API)

optionsApi 使用传统api中,新增一个需求,要在data,methods,computed中修改

compositionApi 我们可以更加优雅的组织我们的代码,函数,让我们的代码更加有序的组合在一起

3.重写虚拟 DOM (Virtual DOM Rewrite)

随着虚拟 DOM 重写,减少 运行时(runtime)开销。重写将包括更有效的代码来创建虚拟节点。

vue3 没有了过滤器

双向数据绑定 从 Object.defineProperty() 变成了 proxy,通过下标修改数组变化了试图数据没发生变化 this.$set() vue3不需要

双向数据绑定原理发生了改变,使用proxy替换Object.defineProerty,使用Proxy的优势:

可直接监听数组类型的数据变

监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升

可直接实现对象属性的新增/删除

setup 函数

3.0新加入了TypeScript以及PWA支持

默认使用懒加载

可以不用加上key

vue3 的watch监听可以进行终止监听

生命周期有了一定的区别 Vue2--------------vue3
beforeCreate  -> setup()    开始创建组件之前,创建的是data和method
created    -> setup()
beforeMount  -> onBeforeMount   组件挂载到节点上之前执行的函数。
mounted    -> onMounted 组件挂载完成后执行的函数
beforeUpdate  -> onBeforeUpdate 组件更新之前执行的函数。
updated    -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount    组件挂载到节点上之前执行的函数。
destroyed   -> onUnmounted  组件卸载之前执行的函数。
activated   -> onActivated  组件卸载完成后执行的函数
deactivated  -> onDeactivated

2-vue生命周期(11个进行扩展延伸)

声明周期那几个?每一个生命周期的特点,可以做什么

beforeCreate() 创建前,这个时候data中的数据,还未定义,所以不能使用
created()创建后 最早开始使用 data和methods中数据的钩子函数
​
beforeMount()挂载前 指令已经解析完毕内存中已经生成dom树,但是尚未挂载到页面中去,此时页面还是旧的。
mounted()挂载后 dom已经渲染完毕,此时页面和内存中都是最新的数据,最早可以操作DOM元素钩子函数
​
 beforeUpdate()更新前 当视图层的数据发生改变会执行这个钩子 内存更新,但是DOM节点还未更新,数据没有与页面同步
 updated()更新后 数据更新完成以后触发的方法,DOM节点已经更新
​
 beforeDestroy()即将销毁 data和methods中的数据此时还是可以使用的,可以做一些释放内存的操作
 destroyed()销毁完毕  组件已经全部销毁,Vue实例已经被销毁,Vue中的任何数据都不可用
 
 其他三个:
activated  被 keep-alive 缓存的组件激活时调用。
deactivated 被 keep-alive 缓存的组件停用时调用。
errorCaptured 2.5.0+ 新增当捕获一个来自子孙组件的错误时被调用
​
Vue3.0中的生命周期做了一些改动:
beforeCreate  -> setup()    开始创建组件之前,创建的是data和method
created       -> setup()
beforeMount   -> onBeforeMount  组件挂载到节点上之前执行的函数。
mounted       -> onMounted  组件挂载完成后执行的函数
beforeUpdate  -> onBeforeUpdate 组件更新之前执行的函数。
Update        - > onUpdated组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount    组件挂载到节点上之前执行的函数。
destroyed     -> onUnmounted    组件卸载之前执行的函数。
​
​
- vue的实例加载完成是在哪个声明周期完成呢
beforeCreate
- vue的dom挂载完成是在哪个声命周期里呢
mounted
​
1、created mounted 的区别?
created 模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
​
​
2、怎么在created里面操作dom?
this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
可以根据打印的顺序看到,在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作并无作用,而在created()里使用this.$nextTick()可以等待dom生成以后再来获取dom对象,而通过this.$nextTick()获取到的值为dom更新之后的值
​
 setTimeout(() => {
      console.log(this.$refs.button);
 });
 
 
3、那 setTimeout this.$nextTick 什么区别呢?
setTimeout 将同步转换为异步 this.$nextTick 
​
 this.$nextTick 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,
​
4、this.$nextTick()是宏任务还是微任务啊? 
 优先是Promise.then方法,是个微任务,这样可以避免多一次队列,进而少一次UI渲染,节省性能
​
​
5、a页面跳转到b页面周期执行
页面a----beforeCreate undefined
页面a----created 1
页面a----beforeMount 1
页面a----mounted 1
页面b----beforeCreate undefined
页面b----created 1
页面b----beforeMount 1
页面a----beforeDestroy 1
页面a----destroyed 1
页面b----mounted 1
​
​
6、组件 和 页面周期 的执行顺序
- 页面beforeCreate undefined
- 页面created 1
- 页面beforeMount 1
- 组件beforeCreate undefined
- 组件created 5555
- 组件beforeMount 5555
- 组件mounted 5555
- 页面mounted 1
​
7、父子组件生命周期执行顺序
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
代码更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
代码销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
代码常用钩子简易版
父create->子created->子mounted->父mounted
​
​
8、补充单一组件钩子执行顺序
​
activated, deactivated 是组件keep-alive时独有的钩子
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeDestroy
destroyed
errorCaptured
​
​
watch
仅仅是数据发生改变的时候会侦听到;
只是会检测到你写在watch里的那些属性,没写的就不会触发。
​
updated
执行到它的时候时候是数据发生变化且界面更新完毕;
不能监听到路由数据(例如网址中的参数);
所有的数据发生变化都会调用(消耗性能);
每次触发的代码都是同一个
​
computed
1、监控自己定义的变量,不用再data里面声明,函数名就是变量名
2、适合多个变量或对象进行处理后返回一个值(结果)。若这多个变量发生只要有一个发生变化,结果都会变化。
3、计算的结果具有缓存,依赖响应式属性变化,响应式属性没有变化,直接从缓存中读取结果。
4、在内部函数调用的时候不用加()。
5、必须用return返回
6、不要在computed 中对data中的数据进行赋值操作,这会形成一个死循环。
​
methods不会被缓存:方法每次都会去重新计算结果。methods 方法表示一个具体的操作,主要书写业务逻辑;
使用 methods 方法编写的逻辑运算,在调用时 add() 一定要加“()”,methods 里面写的多位方法,调用方法一定要有()。methods方法页面刚加载时调用一次,以后只有被调用的时候才会被调用。我们在长度框和宽度框的值输入完以后,点击“+” methods 方法调用一次。这里很明显我们采用 methods 会更节省资源。
​
​
使用场景?
​
watch:
1、watch 函数是不需要调用的。
2、重点在于监控,监控数据发生变化的时候,执行回调函数操作。
3、当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch
4、函数名就是你要监听的数据名字
5、监控一些input框值的特殊处理,适合一个数据影响多个数据。
6、数据变化时,执行一些异步操作,或开销比较大的操作
​
computed:
在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式
一个需要的结果受多个数据影响的时候,比如购物车结算金额(受到很多处的价格结算)。
操作某个属性,执行一些复杂的逻辑,并在多处使用这个结果。
内部函数中多处要使用到这个结果的。
1、监控自己定义的变量,不用再data里面声明,函数名就是变量名
2、适合多个变量或对象进行处理后返回一个值(结果)。若这多个变量发生只要有一个发生变化,结果都会变化。
3、计算的结果具有缓存,依赖响应式属性变化,响应式属性没有变化,直接从缓存中读取结果。
4、在内部函数调用的时候不用加()。
5、必须用return返回
6、不要在computed 中对data中的数据进行赋值操作,这会形成一个死循环。

一般在哪个生命周期请求异步数据

可以啊钩子函数中的 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

在created中最好

能更快获取到服务端数据,减少页面加载时间,用户体验更好;

SSR不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。

mounted 在请求完数据之后需要对 dom 进行操作的时候可以用到

vue中methods,computed,watch的区别

computed 是vue中的计算属性,具有缓存性,当他的依赖于值,发生改变的时候才会重新调用

methods 是没有缓存的,只要调用,就会执行,一般结合事件来使用

watch 没有缓存性 监听data中的属性 属性值只要发生变化就会执行 可以利用他的特性做一些异步的操作

created和mounted区别?

created:dom渲染前调用,即通常初始化某些属性值

mounted:在dom渲染后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作

生命周期钩子是如何实现的

Vue 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好(内部采用数组的方式存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)

3-VUEX常问的考点

vuex严格模式

开启严格模式,仅需在创建 store 的时候传入 strict: true:
 
const store = new Vuex.Store({
  // ...
  strict: true
})
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

vuex是什么,

vuex是一个状态管理工具,所谓状态的是就是数据,采用集中式存储管所有组件的状态,是为了解决中大型项目一个数据共享的问题。vuex 他可以将数据保存到本地,数据是响应式的,能够保持数据页面的共享,提高开发效率

好处

能够在 vuex 中集中管理共享的数据,易于开发和后期维护 可以做状态管理、采用localstorage保存信息、数据一直存储在用户的客户端中 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步,能够高效地实现组件之间的数据共享,提高开发 效率

vuex核心

  1. state:vuex的基本数据,数据源存放地,用于定义共享的数据
  2. getter:从基本数据派生的数据,相当于state的计算属性
  3. mutation:提交更新数据的方法,唯一一个可以操作state中数据的方法,必须是同步的。第一个参数是state,第二个参数是commit传过来的数据
  4. action:用来做异步操作,一般用来发送请求,在action中写入函数,然后在页面中dispatch调用,然后在action中通过commit去调用mutation,通过mutation操作state
  5. modules,模块化vuex,可以让每一个模块拥有自己的state,mutation,action,getters,使结构清晰,方便管理

vuex运行机制

运行机制:Vuex提供数据(state),来驱动视图(这里指的是Vue组件),视图通过Dispatch派发Action,在Action中可以进一步做一些异步的操作(例如通过ajax请求后端的接口数据),然后通过Commit提交给Mutations,由Mutations去最终更改state。那么为什么要经过Mutations呢?这是因为我们要在Vue调试工具(Devtools)中记录数据的变化,这样可以通过插件去进行进一步的调试。所以说Mutations中只能是纯同步的操作,如果是有异步操作,那么就需要在Actions中进行处理。如果说没有异步操作,那么可以直接由组件进行Commit操作Mutations。

高级用法辅助函数(mapState,mapActions,mapMutations,mapGetters)

mapState,mapActions,mapMutations,mapGetters

辅助函数可以把vuex中的数据和方法映射到vue组件中。达到简化操作的目的

如何使用:

Import { mapActions, mapGetters, mapMutations, mapState } from ‘vuex’

computed(){ …mapState([‘数据名字’])}

Vuex 页面刷新数据丢失怎么解决

需要做 vuex 数据持久化 一般使用本地存储的方案来保存数据 可以自己设计存储方案 也可以使用第三方插件

推荐使用 vuex-persist 插件,它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中

Vuex 为什么要分模块并且加命名空间

模块:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。方便管理

4-路由vue-router

vue-router(路由原理?路由守卫?)

由于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模式 默认是hash

vue-router的实现原理(核心):更新视图但不重新请求页面。

1、hash ——即地址栏 URL 中的#符号,它的特点在 于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影 响,因此改变 hash 不会重新加载页面。

2、history ——利用了 HTML5 History api 在浏览器中没有# 有浏览器兼容问题

3、history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,否则返回 404 错误。

全局路由守卫

router.beforeEach 全局前置守卫 进入路由之前

router.beforeResolve 全局解析守卫,在beforeRouteEnter调用之后调用

同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用

router.afterEach 全局后置钩子 进入路由之后

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

组件级路由守卫 放在要守卫的组件里,跟data和methods同级

  • beforeRouteEnter 进入路由前,此时实例还没创建,无法获取到zhis
  • beforeRouteUpdate (2.2) 路由复用同一个组件时
  • beforeRouteLeave 离开当前路由,此时可以用来保存数据,或数据初始化,或关闭定时器等等
//在组件内部进行配置,这里的函数用法也是和beforeEach一毛一样
const Foo = {
  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() }

参数

  • to: Route: 即将要进入的目标 路由对象
  • from: Route: 当前导航正要离开的路由对象
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

重定向用哪个属性

redirect:”/路径”

vue路由的跳转方式有几种

1、<router-link to="需要跳转到页面的路径"> 2、this.$router.push()跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面

3、this.$router.replace()跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面

4、this.$touter.go(n)向前或者后跳转n个页面,n可以是正数也可以是负数

router.push、router.replace、router.go、router.back的区别?

router.push:跳转,并向history栈中加一个记录,可以后退到上一个页面

router.replace:跳转,不会向history栈中加一个记录,不可以后退到上一个页面

router.go:传正数向前跳转,传负数向后跳转

router.back 返回到上一级页面

vue 路由传参数如何实现、query 和 params

主要通过 query 和 params 来实现

(1) query可以使用name和path而params只能使用name

(2) 使用params传参刷新后不会保存,而query传参刷新后可以保存

(3) Params在地址栏中不会显示,query会显示

(4) Params可以和动态路由一起使用,query不可以

(5)to=”/goods?id=1001”this.然后在接收的页面通过 $route.query.id 来接收

路由对象route和router的区别

route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。

router 是“路由实例对象”,包括了路由的跳转方法(push、go),钩子函数等。

vue-router 路由钩子函数是什么 执行顺序是什么执行顺序

一、打开页面的任意一个页面,没有发生导航切换。
全局前置守卫beforeEach (路由器实例内的前置守卫)
路由独享守卫beforeEnter(激活的路由)
组件内守卫beforeRouteEnter(渲染的组件)
全局解析守卫beforeResolve(路由器实例内的解析守卫)
全局后置钩子afterEach(路由器实例内的后置钩子)

二、如果是有导航切换的(从一个组件切换到另外一个组件)
组件内守卫beforeRouteLeave(即将离开的组件)
全局前置守卫beforeEach (路由器实例内的前置守卫)
组件内守卫beforeRouteEnter(渲染的组件)
全局解析守卫beforeResolve(路由器实例内的解析守卫)
全局后置钩子afterEach(路由器实例内的后置钩子)


完整的导航解析流程
导航被触发。
在失活的组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

动态路由

动态路由是指路由器能够自动的建立自己的路由表,能够根据实际情况的变化实时地进行调整。用开头,后面跟的值是不确定的。这个值是我们要传递的参数 动态路由匹配本质上就是通过url进行传参

    比如在写一个商品详情页面的时候,我们的页面结构都一样,只是渲染的数据不同而已,这时候就可以根据商品的不同id去设置动态路由,只需要写一个组件,就可以把每个商品的商品详情映射到同一个组件上去。
    { 
        path: '/Jqxq/:id', // 路由配置拼接
        name: 'Jqxq',
        component: Jqxq
    }
    跳转 this.$router.push('/Jqxq/'+ item.id)
    接收  : this.$route.params.id

嵌套路由

vue项目中,界面通常由多个嵌套的组件构成, 必须先清楚这样一件事,一个对应展示的就是一个组件 因此实现嵌套路由有两个要点: 路由对象中定义子路由 用children实现嵌套路由 组件内的使用.

路由的配置和参数

export default new Router({
    mode: 'history', //路由模式,取值为history与hash
    base: '/', //打包路径,默认为/,可以修改
    routes: [
    {
        path: string, //路径
        ccomponent: Component; //页面组件
        name: string; // 命名路由-路由名称
        components: { [name: string]: Component }; // 命名视图组件
        redirect: string | Location | Function; // 重定向
        props: boolean | string | Function; // 路由组件传递参数
        alias: string | Array<string>; // 路由别名
        children: Array<RouteConfig>; // 嵌套子路由
        // 路由单独钩子
        beforeEnter?: (to: Route, from: Route, next: Function) => void; 
        meta: any; // 自定义标签属性,比如:是否需要登录
        icon: any; // 图标
        // 2.6.0+
        caseSensitive: boolean; // 匹配规则是否大小写敏感?(默认值:false)
        pathToRegexpOptions: Object; // 编译正则的选项
    }
    ]
})

怎么定义 vue-router 的动态路由? 怎么获取传过来的值

在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 route 对象的 params.id 获取

路由懒加载

使用原因:在单页应用中,如果没有应用懒加载,运用 webpack 打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时 原理:vue 异步组件技术:异步加载,vue-router 配置路由 , 使用 vue 的异步组件技术 , 实现按需加载。

{ path: ‘/home’, component: () => import(‘@/views/home/home.vue’) } // 懒加载

能说下 vue-router 中常用的路由模式实现原理吗

hash 模式
location.hash 的值实际就是 URL 中#后面的东西 它的特点在于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。

可以为 hash 的改变添加监听事件
window.addEventListener(“hashchange”, funcRef, false);

每一次改变 hash(window.location.hash),都会在浏览器的访问历史中增加一个记录利用 hash 的以上特点,就可以来实现前端路由“更新视图但不重新请求页面”的功能了
特点:兼容性好但是不美观

history 模式
利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。
这两个方法应用于浏览器的历史记录站,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。

5-常用指令

vue常用修饰和常见指令

修饰符
.stop  阻止事件冒泡
.cpture 设置事件捕获
.self  只有当事件作用在元素本身才会触发
.prevent 阻止默认事件,比如超链接跳转
.once 事件只能触发一次
.native 触发js原生的事件
.number 把文本框的内容转换为数字
.trim  去除文本框左右空格
常见指令
⑴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-if与v-for优先级

v-for 比 v-if 优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。

vue中key的作用

“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中是这样操作的,删除后新的数据这时会进行比较,第一个节点的标签一样,值不一样,就会复用原来位置的标签,不会做删除和创建,在第一个节点中是将复选框选中的,当我们看见好像是把第一个删除了,但是点击后去看复选框的时候还是选中在第一个,如果是直接将第一个节点删除了那么复选框就不会选中。

vue 初始化页面闪动问题。

能够解决插值表达式闪烁问题,需要在style中设置样式[v-clock]{display:none}

v-if和v-show的区别及使用场景?

v-if 动态的创建或者销毁元素,为true的时候为显示,为false的时候不显示,要使用v-else必须和v-if紧挨着

v-show 是控制元素的显示或者隐藏,在我们的标签上会看到有display:block,none

v-if 有更高的切换消耗,而 v-show 有更高的初始化渲染消耗,一般推荐频繁切换的时候使用 v-show 更好,当我们的判断分支比较多的时候,和首次渲染的时候 使用v-if

自定义指令,自定义过滤器

出了vue自带的指定以外,我们如果需要对dom进行底层操作的时候这里就用到了自定义指令,分为一下

全局: vue.directive:{“”,{}} 局部:directives:{指令名:{钩子函数}}

  1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

  2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

  3. update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

  4. componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

  5. unbind:只调用一次,指令与元素解绑时调用。

参数

el:指令所绑定的元素

binding:一个对象包含一下,

name:指令名,不包括 v- 前缀。

value:指令的绑定值

自定义指令原理

1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性

2.通过 genDirectives 生成指令代码

3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子

4.当执行指令对应钩子函数时,调用对应指令定义的方法

6-选项对象和常用api

过滤器

过滤器是对 即将显示的数据做进一步的筛选处理,然后显示,过滤器并没有改变原来的数据,只是在原数据的基础上产生新的数据

全局:

Vue.filter(‘过滤器名’,funciton(val){})

局部过滤器,定义在组件内部 filters 属性上.它只能在此组件内部使用.

filters:{过滤器名:funciton(参数){//逻辑代码}}

使用: 过滤时间,过滤金钱

mixin

Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。 mixin 项目变得复杂的时候,多个组件间有重复的逻辑就会用到mixin

如果希望在多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。

然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。

在vue.js中mixin和页面执行顺序问题

mixin中的代码先执行,单文件中的后执行。

page的beforeCreate --> mixin的beforeCreate --> page的created --> mixin的created --> page的beforeMount --> mixin的beforeMount -->page的mounted -->mixin的mounted

nextTick 使用场景和原理

在下次DOM更新循环结束后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。使用场景是:可以在created钩子函数中拿到dom节点

nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法

vue的删除数组和原生删除数组的区别delete

删除数组

  1. delete只是把数组元素的值变成empty/undefined,元素的键不变,数组长度不变。
  2. Vue.delete直接删除数组,改变数组的键值和长度。

删除对象

两者相同,都会把键名(属性/字段)和键值删除。

Vue.extend 作用和原理

官方解释:Vue.extend 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

其实就是一个子类构造器 是 Vue 组件的核心 api 实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并 基础用法

<div id="mount-point"></div>
// 创建构造器
/* Vue.extend( options )
  参数:{Object} options
  用法:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象;
       data 选项是特例,需要注意: 在 Vue.extend() 中它必须是函数;*/
var Profile = Vue.extend({
  template: '<p>{
   
   {firstName}} {
   
   {lastName}} aka {
   
   {alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
​
// 结果如下:
<p>Walter White aka Heisenberg</p>
​
/*
可以看到,extend 创建的是 Vue 构造器,而不是我们平时常写的组件实例,所以不可以通过 new Vue({ components: testExtend }) 来直接使用,需要通过 new Profile().$mount(’#mount-point’) 来挂载到指定的元素上。
*/
​

7-组件模块部分(插槽,单页面,通信)

vue父子,子父,兄弟通信

父传递子如何传递

(1)在父组件的子组件标签上绑定一个属性,挂载要传输的变量 (2)在子组件中通过props来接受数据,props可以是数组也可以是对象,接受的数据可以直接使用 props: [“属性名”] props:{属性名:数据类型}

子传递父如何传递

(1)在父组件的子组件标签上自定义一个事件,然后调用需要的方法 (2)在子组件的方法中通过 this.$emit(“事件”)来触发在父组件中定义的事件,数据是以参数的形式进行传递的

兄弟组件如何通信

(1)找到min.js文件,给他vue挂载一个 公共的 b u s V u e . p r o t o t y p e . bus Vue.prototype. busVue.prototype.bus = new Vue() (2)传送数据的一方 用this. b u s . bus. bus.emit(‘事件名’,‘传送的数据’) (3)在 接收数据的一方用通过 Bus.$on(“事件名”,(data)=>{data是接受的数据})

props验证,和默认值

props:会接收不同的数据类型,常用的数据类型的设置默认值的写法,Number, String, Boolean, Array, Function, Object

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级prop 的更新流动到子组件中,但是反过来则不行。这样防止子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。若果在子组件中直接修改prop传递的值,Vue会发出警告,

组件中写 name 选项有什么作用

① 项目使用 keep-alive 时,可搭配组件的 name 进行缓存过滤。 ② DOM 做递归组件时需要调用自身 name ③ vue-devtools 调试工具里显示的组件名称是由 vue 中组件 name 决定的

自定义组件

在vue 中的 component 中新建组件,定义好视图层,

在vue中开发,都是用的组件化的思想开发的,组件封装的方式可以使我们的开发效率提高,把单页面的每个模块拆分为一个组件件,

组件封装的方式解决了我们传统项目,开发效率低,难以维护,复用性低等问题。

使用:比如说封装一个 swiper 首先我们要定义一个props要接受传递的数据,写入响应的逻辑,在通过import引入到页面注册作为标签使用即可。

keep-alive作用

keep-alive是Vue提供给我们一个内置组件,会缓存不活动的组件实例,而不是销毁它们, 作为标签使用 包裹在需要缓存的组件外

在组件切换过程中 把切换出去的组件保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性

作用: 比如列表页面进入详情,我们想保存列表滚动的位置,我们就可以使用keep-alive保存列表页面的滚动位置。

组件使用keep-alive以后会新增两个生命周期 actived() deactived()

activated(组件激活时使用) 与 deactivated(组价离开时调用)

有两个参数: 允许组件有条件的进行缓存。

include - 包裹的组件名会被缓存

exclude 包裹的组件名都不会被缓存

keep-alive 缓存beforDestroy 还会执行吗

首先,答案是不会的,准确的说是不会直接调用。 默认情况下,也就是没有设置keep-alive,当离开当前路由时,会直接调用beforeDestroy和destroyed来销毁。 当组件设置keep-alive后,不会直接调用这个销毁周期函数,而是会在生命周期函数新增两个,activated和deactivated; 当退出的时候会执行deactivated 函数

VUE组件中的data为什么是一个函数

Object是引用数据类型,如果不用function返回,每个组件的data都是内存的同一个地址,一个数据改变了其他也改变了,这就造成了数据污染。如果data是一个函数,每个实例的data都在闭包中,就不会各自影响了

组件特性及好处、组件的基本组成

(1) 特性:重用性、可指定性、互操作性、高内聚性、低耦合度

(2) 好处:组件可以扩展HTML元素、封装可重用代码

template 结构(html代码)

script行为

style样式

什么是slot?什么是命名slot?slot怎么使用?

插槽就是父组件往子组件中插入一些内容。

有三种方式,默认插槽,具名插槽,作用域插槽

默认插槽就是把父组件中的数据,显示在子组件中,子组件通过一个slot插槽标签显示父组件中的数据

具名插槽是在父组件中通过slot属性,给插槽命名,在子组件中通过slot标签,根据定义好的名字填充到对应的位置。这样就可以指定多个可区分的slot,在使用组件时灵活地进行插值。

作用域插槽是带数据的插槽,子组件提供给父组件的参数,父组件根据子组件传过来的插槽数据来进行不同的展现和填充内容。在标签中通过v-slot=""要穿过来的数据“来接受数据。

scoped 原理及穿透方法

vue 中的 scoped 通过在 DOM 结构以及 css 样式上加唯一不重复的标记:data-v-hash 的方式,以保证唯一(通过 PostCSS 转译),达到样式私有模块化的目的。

scoped 的 3 条渲染规则: ① 给 HTML 的 DOM 节点加一个不重复的 data 属性,来表示它的唯一性; ② 在每句 css 选择器末尾(编译后的生成的 css 语句)加一个当前组件的 data 属性选择器来私有化样式; ③ 如果组件内部包含有其他组件,只会给其他组件的最外层标签加上 ddan 当前组件的 data 属性

在做项目中,会遇到这么一个问题,即:引用了第三方组件,需要在组件中局部修改第三方组件的样式,而又不想去除scoped属性造成组件之间的样式污染。那么有哪些解决办法呢?
①不使用scopeds省略(不推荐);
② 在模板中使用两次style标签。
③scoped穿透:/deep/ >>>

函数式组件使用场景和原理

函数式组件与普通组件的区别
​
1.函数式组件需要在声明组件是指定 functional:true
2.不需要实例化,所以没有this,this通过render函数的第二个参数context来代替
3.没有生命周期钩子函数,不能使用计算属性,watch
4.不能通过$emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件
5.因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement
6.函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)
​
​
优点 1.由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件 2.函数式组件结构比较简单,代码结构更清晰
使用场景:
一个简单的展示组件,作为容器组件使用 比如 router-view 就是一个函数式组件
“高阶组件”——用于接收一个组件作为参数,返回一个被包装过的组件

8-VUE项目中的问题

单页面应用和多页面应用的优缺点

单页面:只有一个html页面,跳转方式是组件之间的切换

优点:跳转流畅、组件化开发、组件可复用、开发便捷

缺点:首屏加载过慢

多页面:有多个页面,跳转方式是页面之间的跳转

优点:首屏加载块

缺点:跳转速度慢

口述axios封装

首先要安装axios,一般我会在项目的src目录中,新建一个network文件夹,作为我们的网络请求模块,然后在里面新建一个http.js和一个api.js文件和一个reques.js。

http.js文件用来封装我们的axios basUrl Tiemout,

api.js用来统一管理我们的接口url,

在request.js中添加请求拦截和响应拦截。在请求拦截中,会给请求头添加token字段,还有loading动画的开启。在响应拦截中,可以做一些loading动画的关闭,还有可以根据后端返回的状态码,做一些检验token是否有效或者过期的操作。

接着就是做一些axios进行的api接口的封装,这里我用到了async,await封装请求接口函数,这样可以将异步操作同步化操作,代码更加友好,避免回调地域的出现

vue中如何解决跨域

在vue开发中实现跨域:在vue项目根目录下找到vue.config.js文件(如果没有该文件则自己创建),在proxy中设置跨域

 
devServer: {
    proxy: {  //配置跨域
      '/api': {
        target: 'http://121.121.67.254:8185/',  //这里后台的地址模拟的;应该填写你们真实的后台接口
        changOrigin: true,  //允许跨域
        pathRewrite: {
          /* 重写路径,当我们在浏览器中看到请求的地址为:http://localhost:8080/api/core/getData/userInfo 时
            实际上访问的地址是:http://121.121.67.254:8185/core/getData/userInfo,因为重写了 /api
           */
          '^/api': '' 
        }
      },
    }
  },

assets和static的区别?

assets中的文件会经过webpack打包,重新编译,推荐在assets存放js等需要打包编译的文件。

static中的文件,不会打包编译。static中的文件只是复制一遍。static中建议放一些外部第三方文件,自己的放assets里,别人的放static中。(图片推荐放在static里)

Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?

不会立即同步执行重新渲染。Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。

如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环tick中,Vue 刷新队列并执行实际(已去重的)工作。

多环境变量

首先是通过在根目录下创建.env.*(配置文件)文件,development 本地开发环境配置、staging 测试环境配置、production 正式环境配置(生产环境)。因为我在创建的文件中并没有定义很多变量,只定义了基础的env,所以需要在src目录下创建一个config文件夹,创建对应的环境变量文件,用来管理不同的环境。在config中创建对应的文件是为了后期修改起来方便,不需要重启项目,符合开发习惯。之后就是根据需要的环境,在封装的axios中通过解构赋值的方式导入,放在baseURL中就可以使用。

element-ui和vant-ui按需引入

首先安装按需引入的插件,在babel.config.js中添加按需引入的配置,创建一个plugin文件夹,定义一个js文件用来存放按需引入的代码,之后在建好的js文件中首先导入vue,再导入需要的vant-ui插件,通过vue.use()全局注入。修改样式可以用样式穿透 /deep/

Vue 解决了什么问题

① 虚拟 dom:dom 操作时非常耗性能的,不再使用原生的 dom 操作节点,极大的解放 dom 操作,但具体操作的还是 dom,不过是换了一种方式。提供了很多指令当然需要 对 dom 进行底层操作的时候就用到自定义指令

② 视图、数据、结构分离:使数据的更改更为简单,只需要操作数据就能完成相关操作。

③ 组件化:把一个单页应用中的各种模块拆分到一个一个单独的组件中,便于开发,以及后期的维护

Vue.js 的特点

简洁:页面由 HTML 模板+Json 数据+Vue 实例组成 数据驱动:自动计算属性和追踪依赖的模板表达式

组件化:用可复用、解耦的组件来构造页面 轻量:代码量小,不依赖其他库 快速:精确有效批量 DOM 更新 模板友好:可通过 npm,bower 等多种方式安装,很容易融入 Vue 的核心库只关注视图层,并且非常容易学习

请说出 vue.cli 项目中 src 目录每个文件夹和文件的用法

assets 文件夹是放静态资源;

components 是放组件;

router 是定义路由相关的配置;

view 视图;

app.vue 是一个应用主组件;

main.js 是入口文件

描述下 vue 从初始化页面–>修改数据–>刷新页面 UI 过程?

当 Vue 进入初始化阶段时,一方面 Vue 会遍历 data 中的属性,并用 Object.defineProperty 将它转化成 getter/setterd 的形式,实现数据劫持;另一方面,Vue 的指令编译器 Compiler 对元素节点的各个指令进行解析,初始化视图,并订阅 Watcher 来更新视图,此时 Watcher 会将自己添加到消息订阅器 Dep 中,此时初始化完毕。
当数据发生变化时,触发 Observer 中 setter 方法,立即调用 Dep.notify( ),Dep 这个数组开始遍历所有的订阅者,并调用其 update 方法,Vue 内部再通过 diff 算法,patch 相应的更新完成对订阅者视图的改变。

Vue 怎么重置 data

使用 Object.assign(),vm.d a t a 可 以 获 取 当 前 状 态 下 的 d a t a ,

Object.assign(this. d a t a , t h i s . data, this. data,this.options.data())

vue-router 登陆权限的判断

vue-router的登陆权限判断主要是在全局钩子函数中进行的,我们在router.js文件中的定义路由里,将需要登陆权限的页面加上meta属性,值是对象的形式,然后在该对象中自定义一个属性,属性值就是一个Boolean值,这时候在main.js文件的全局钩子函数中进行判断,如果需要跳转的页面的自定义属性值为true,那么将进行判断其是否登录,如果没有登录,则告诉用户登录,如果有登录,那么进行页面跳转。

如何解决vue首屏加载过慢?

① 不常改变的库放到 index.html 中,通过 cdn 引入,然后找到 build/webpack.base.conf.js 文件,在 module.exports = { } 中添加以下代码:

externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
}

②vue 路由懒加载,图片懒加载,使用异步组件,按需加载

③ 不生成 map 文件,找到 config/index.js 文件,修改为 productionSourcceMap:false

④vue 组件尽量不要全局引入

⑤ 使用更轻量级的工具库

⑥ 开启 gzip 压缩:这个优化是两方面的,前端将文件打包成.gz 文件,然后通过 nginx 的配置,让浏览器直接解析.gz 文件。

⑦ 首页单独做服务端渲染:如果首页真的有瓶颈,可以考虑用 node 单独做服务端渲染,而下面的子页面仍用 spa 单页的方式交互。这里不推荐直接用 nuxt.js 服务端渲染方案,因为这样一来增加了学习成本,二来服务端的维护成本也会上升,有时在本机测试没问题,在服务端跑就有问题,为了省心,还是最大限度的使用静态页面较好。

Vue和JQuery的区别在哪?为什么放弃JQuery用Vue?

jQuery是直接操作DOM,Vue不直接操作DOM,Vue的数据与视图是分开的,Vue只需要操作数据就行它是个框架

jQuery的操作DOM行为是频繁的,而Vue利用虚拟DOM的技术,大大提高了更新DOM时的性能它是个库

Vue中不倡导直接操作DOM,开发者只需要把大部分精力放在数据层面上

Vue集成了一些库,大大提高开发效率,例如Route、Vuex等等

你都做过哪些Vue的性能优化?

尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher v-if和v-for不能连用 v-if 和 v-show 区分使用场景 v-for 遍历必须加 key,key 最好是 id 值,且避免同时使用 v-if 如果需要使用v-for给每项元素绑定事件时使用事件代理

SPA 页面采用keep-alive缓存组件 在更多的情况下,使用v-if替代v-show 使用路由懒加载、异步组件

防抖、节流 第三方模块按需导入

长列表滚动到可视区域动态加载,不需要响应式的数据不要放到 data 中(可以Object.freeze() 冻结数据)

图片懒加载

SEO优化 预渲染

服务端渲染SSR 打包优化,

压缩代码 Tree Shaking/Scope Hoisting

使用cdn加载第三方模块 多线程打包happypack splitChunks抽离公共文件 sourceMap优化 骨架屏

PWA 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。防止内部泄漏,组件销毁后把全局变量和事件销毁

源;

components 是放组件;

router 是定义路由相关的配置;

view 视图;

app.vue 是一个应用主组件;

main.js 是入口文件

描述下 vue 从初始化页面–>修改数据–>刷新页面 UI 过程?

当 Vue 进入初始化阶段时,一方面 Vue 会遍历 data 中的属性,并用 Object.defineProperty 将它转化成 getter/setterd 的形式,实现数据劫持;另一方面,Vue 的指令编译器 Compiler 对元素节点的各个指令进行解析,初始化视图,并订阅 Watcher 来更新视图,此时 Watcher 会将自己添加到消息订阅器 Dep 中,此时初始化完毕。
当数据发生变化时,触发 Observer 中 setter 方法,立即调用 Dep.notify( ),Dep 这个数组开始遍历所有的订阅者,并调用其 update 方法,Vue 内部再通过 diff 算法,patch 相应的更新完成对订阅者视图的改变。

Vue 怎么重置 data

使用 Object.assign(),vm.d a t a 可 以 获 取 当 前 状 态 下 的 d a t a ,

Object.assign(this. d a t a , t h i s . data, this. data,this.options.data())

vue-router 登陆权限的判断

vue-router的登陆权限判断主要是在全局钩子函数中进行的,我们在router.js文件中的定义路由里,将需要登陆权限的页面加上meta属性,值是对象的形式,然后在该对象中自定义一个属性,属性值就是一个Boolean值,这时候在main.js文件的全局钩子函数中进行判断,如果需要跳转的页面的自定义属性值为true,那么将进行判断其是否登录,如果没有登录,则告诉用户登录,如果有登录,那么进行页面跳转。

如何解决vue首屏加载过慢?

① 不常改变的库放到 index.html 中,通过 cdn 引入,然后找到 build/webpack.base.conf.js 文件,在 module.exports = { } 中添加以下代码:

externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
}

②vue 路由懒加载,图片懒加载,使用异步组件,按需加载

③ 不生成 map 文件,找到 config/index.js 文件,修改为 productionSourcceMap:false

④vue 组件尽量不要全局引入

⑤ 使用更轻量级的工具库

⑥ 开启 gzip 压缩:这个优化是两方面的,前端将文件打包成.gz 文件,然后通过 nginx 的配置,让浏览器直接解析.gz 文件。

⑦ 首页单独做服务端渲染:如果首页真的有瓶颈,可以考虑用 node 单独做服务端渲染,而下面的子页面仍用 spa 单页的方式交互。这里不推荐直接用 nuxt.js 服务端渲染方案,因为这样一来增加了学习成本,二来服务端的维护成本也会上升,有时在本机测试没问题,在服务端跑就有问题,为了省心,还是最大限度的使用静态页面较好。

Vue和JQuery的区别在哪?为什么放弃JQuery用Vue?

jQuery是直接操作DOM,Vue不直接操作DOM,Vue的数据与视图是分开的,Vue只需要操作数据就行它是个框架

jQuery的操作DOM行为是频繁的,而Vue利用虚拟DOM的技术,大大提高了更新DOM时的性能它是个库

Vue中不倡导直接操作DOM,开发者只需要把大部分精力放在数据层面上

Vue集成了一些库,大大提高开发效率,例如Route、Vuex等等

你都做过哪些Vue的性能优化?

尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher v-if和v-for不能连用 v-if 和 v-show 区分使用场景 v-for 遍历必须加 key,key 最好是 id 值,且避免同时使用 v-if 如果需要使用v-for给每项元素绑定事件时使用事件代理

SPA 页面采用keep-alive缓存组件 在更多的情况下,使用v-if替代v-show 使用路由懒加载、异步组件

防抖、节流 第三方模块按需导入

长列表滚动到可视区域动态加载,不需要响应式的数据不要放到 data 中(可以Object.freeze() 冻结数据)

图片懒加载

SEO优化 预渲染

服务端渲染SSR 打包优化,

压缩代码 Tree Shaking/Scope Hoisting

使用cdn加载第三方模块 多线程打包happypack splitChunks抽离公共文件 sourceMap优化 骨架屏

PWA 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。防止内部泄漏,组件销毁后把全局变量和事件销毁

猜你喜欢

转载自blog.csdn.net/m0_37408390/article/details/127902762
今日推荐