最后
你要问前端开发难不难,我就得说计算机领域里常说的一句话,这句话就是『难的不会,会的不难』,对于不熟悉某领域技术的人来说,因为不了解所以产生神秘感,神秘感就会让人感觉很难,也就是『难的不会』;当学会这项技术之后,知道什么什么技术能做到什么做不到,只是做起来花多少时间的问题而已,没啥难的,所以就是『会的不难』。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
我特地针对初学者整理一套前端学习资料
var node = document.createElement(“div”);
var attr = document.createAttribute(“class”);
var text = document.createTextNode(“菜呀菜”); 插入DOM节点 node.appendChild(text) //插入新的子节点 node.insertBefore(pre,child) //在node元素内child前加入新元素
删除DOM节点
node.removeChild(text) //从父元素删除子元素节点
修改DOM节点
node.setAttribute(“class”,“name”) //修改设置属性节点
node.replaceChild(pre,child) //父节点内新子节点替换旧子节点 常用DOM属性 node.innerHtml //获取/替换元素内容 node.parentNode //元素节点的父节点 node.parentElement //元素节点的父元素节点(一般与Node节点相同) node.firstChild //属性的第一个节点 node.lastChild //属性的最后一个节点 node.nextSibling //节点元素后的兄弟元素(包括回车,空格,换行) node.nextElementSibling //节点元素后的兄弟元素节点 node.previousSibling //获取元素的上一个兄弟节点(元素,文本,注释) node.previousElementSibling //获取元素的上一个兄弟节点(只包含元素节点) node.childNodes //元素节点的子节点(空格,换行默认为文本节点) node.children //返回当前元素的所有元素节点 node.nodeValue //获取节点值 node.nodeName //获取节点名字 node.attributes //元素节点的属性节点 node.getAttribute("name") //元素节点的某个属性节点 node.style.width = "200px" //设置css样式
#### 常用的api
#### offset、client、scroll的用法?
>
> offset系列 经常用于获得元素位置 offsetLeft offsetTop
>
>
> client经常用于获取元素大小 clientWidth clientHeight
>
>
> scroll 经常用于获取滚动距离 scrollTop scrollLeft
>
>
>
### js面试题的扩展
#### 什么是函数式编程? 命令式编程?声明式编程?
>
> **声明式编程:专注于”做什么”而不是”如何去做”。**在更高层面写代码,更关心的是目标,而不是底层算法实现的过程。 如:css, 正则表达式,sql 语句,html, xml…
>
>
> **命令式编程(过程式编程) : 专注于”如何去做”**,这样不管”做什么”,都会按照你的命令去做。解决某一问题的具体算法实现。
>
>
> 如: for()
>
>
> 函数式编程:把运算过程尽量写成一系列嵌套的函数调用。
>
>
> 如 : forEach()
>
>
>
#### iframe的优缺点有哪些?
>
> 优点:
> ①iframe能够原封不动的把嵌入的网页展现出来;
> ②如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
> ③网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
> ④如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。
>
> 缺点:
> ①会产生很多页面不易管理;
> ②iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
> ③代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化。
> ④很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。
> ⑤iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。
>
>
>
#### 如何让(a == 1 && a == 2 && a == 3)的值为true?
" == "操作符在左右数据不一致的时候,会先进行隐式转换,该值意味着不是基本数据类型,
因为如果a是null或者undefined、bool类型都不可能返回true;可以推测a是复杂数据类型。
方法一:数组的 toString 接口默认调用数组的 join 方法,重新 join 方法
let a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3) //true
方法二:利用数据劫持(Proxy/Object.definedProperty)
let i = 1;
let a = new Proxy({},{
i:1,
get:function(){
return () => this.i++
}
});
console.log(a == 1 && a == 2 && a == 3);
#### 为什么0.1+0.2 ! == 0.3,如何让其相等
在开发过程中遇到类似这样的问题:
let n1 = 0.1, n2 = 0.2
console.log(n1 + n2) // 0.30000000000000004
这里得到的不是想要的结果,要想等于0.3,就要把它进行转化:
(n1 + n2).toFixed(2) // 注意,toFixed为四舍五入
复制代码
toFixed(num) 方法可把 Number 四舍五入为指定小数位数的数字。那为什么会出现这样的结果呢?
计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和
##
## es6部i分面试题
#### 1、 ES6 新增特性
>
> 1. 新增了块级作用域(let,const)
> 2. 提供了定义类的语法糖(class)
> 3. 新增了一种基本数据类型(Symbol)
> 4. 新增了变量的解构赋值
> 5. 函数参数允许设置默认值,引入了 rest 参数,新增了箭头函数
> 6. 数组新增了一些 API,如 isArray / from / of 方法;数组实例新增了entries(),keys() 和 values() 等方法
> 7. 对象和数组新增了扩展运算符
> 8. ES6 新增了模块化(import/export)
> 9. ES6 新增了 Set 和 Map 数据结构
> 10. ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例
> 11. ES6 新增了生成器(Generator)和遍历器(Iterator)
>
>
>
#### 2、require与import的区别和使用(CommonJS规范和es6规范)
>
> 1、**import是ES6中的语法标准也是用来加载模块文件的**,import函数可以读取并执行一个JavaScript文件,然后返回该模块的export命令指定输出的代码。**export与export default均可用于导出常量、函数、文件、模块**,**export可以有多个,export default只能有一个。**
>
>
> 2、**require 定义模块**:**module变量代表当前模块**,它的exports属性是对外的接口。通过exports可以将模块从模块中导出,其他文件加载该模块实际上就是读取module.exports变量,他们可以是变量、函数、对象等。在node中如果用exports进行导出的话系统会系统帮您转成module.exports的,只是导出需要定义导出名。
>
>
> **require与import的区别**
>
>
> **1,require是CommonJS规范的模块化语法,import是ECMAScript 6规范的模块化语法;**
>
>
> **2,require是运行时加载,import是编译时加载;**
>
>
> **3,require可以写在代码的任意位置,import只能写在文件的最顶端且不可在条件语句或函数作用域中使用;**
>
>
> 4,require通过module.exports导出的值就不能再变化,import通过export导出的值可以改变;
>
>
> 5;require通过module.exports导出的是exports对象,import通过export导出是指定输出的代码;
>
>
> 6,require运行时才引入模块的属性所以性能相对较低,import编译时引入模块的属性所所以性能稍高。
>
>
>
#### 3、箭头函数
>
> **js中我们在调⽤函数的时候经常会遇到this作⽤域的问题,这个时候ES6给我们提箭头函数**。
>
>
> **1、 箭头函数是匿名函数不能作为构造函数,不能使用new**
>
>
> **2、 箭头函数不绑定arguments,取而代之用rest参数…解决,**
>
>
> **3、 this指向不同,箭头函数的this在定义的时候继承自外层第一个普通函数的this**
>
>
> **4、 箭头函数通过call()或apply()调用一个函数,只传入了一个参数,对this并没有影响.**
>
>
> **5、 箭头函数没有prototype(原型),所以箭头函数本身没有this**
>
>
> **6、 箭头函数不能当做Generator函数,不能使用yield关键字、**
>
>
> **7、 写法不同,箭头函数把function省略掉了 ()=> 也可以吧return 省略调 写法更简洁**
>
>
> **8、箭头函数不能通过call()、apply()、bind()方法直接修改它的this指向。**
>
>
>
#### 4、简述 let const var 的区别 以及使用场景
* var let 是用来声明变量的,而const是声明常量的 var
>
>
> ```
> 1.var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined
> 2、一个变量可多次声明,后面的声明会覆盖前面的声明
> 3、在函数中使用var声明变量的时候,该变量是局部的作用域只在函数内部,而如果在函数外部使用 var,该变量是全局的
>
> ```
>
>
* let
>
>
> ```
> 1、不存在变量提升,let声明变量前,该变量不能使用。就是 let 声明存在暂时性死区
> 2、let命令所在的代码块内有效,在块级作用域内有效,作用域只是在花括号里面
> 3、let不允许在相同作用域中重复声明,注意是相同作用域,不同作用域有重复声明不会报错
>
> ```
>
>
* const
>
>
> ```
> 1、const声明一个只读的常量,声明后,值就不能改变
> 2、let和const在同一作用域不允许重复声明变量const声明一个只读的常量。一旦声明,常量的值就不能改变,但对于对象和数据这种 引用类型,内存地址不能修改,可以修改里面的值。
> 3、let和const不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错
>
> ```
>
> 4、能用const的情况下尽量使用const,大多数情况使用let,避免使用var。 const > let > var const声明的好处,一让阅读代码的人知道该变量不可修改,二是防止在修改代码的过程中无意中修改了该变量导致报错,减少bug的产生
>
>
>
#### 5、map和forEach的区别
>
> **相同点**
>
>
> 都是循环遍历数组中的每一项 **forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组)**,需要用哪个的时候就写哪个 匿名函数中的this都是指向window 只能遍历数组
>
>
> **注意**:forEach对于空数组是不会调用回调函数的。
>
>
>
>
> **不同点**
>
>
> **map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值**。(原数组进行处理之后对应的一个新的数组。) **map()方法不会改变原始数组 map()方法不会对空数组进行检测 forEach()方法用于调用数组的每个元素,将元素传给回调函数.(没有return,返回值是undefined)**
>
>
>
#### 6、promise的解释
>
> **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会返回一个新的 promise 对象,能保证 then 方 可以进行链式调用
>
>
>
补充:
#### Promise.all哪怕一个请求失败了也能得到其余正确的请求结果的解决方案
Promise.all默认只要有一个错误就直接返回错误。promise.all中任何一个promise 出现错误的时候都会执行reject,导致其它正常返回的数据也无法使用
Promise.all(
[
Promise.reject({ code: 500, msg: “服务异常” }),
Promise.resolve({ code: 200, list: [] }),
Promise.resolve({ code: 200, list: [] })
].map(p => p.catch(e => e))
)
.then(res => {
console.log(“res=>”, res);
})
.catch(error => {
console.log(“error=>”, error);
});
res=> [ { code: 500, msg: ‘服务异常’ },
{ code: 200, list: [] },
{ code: 200, list: [] } ]
核心内容是map方法,map的每一项都是promise,catch方法返回值会被promise.reslove()包裹,这样传进promise.all的数据都是resolved状态的。
// 使用Promise.all 其中id为69的商品,返回失败,会导致整个Promise接受到reject状态.
// 所以进行改造, p catch 得到的err 为返回失败抛出的信息, 进行置空
.map(p => p.catch(err => ‘’)))
#### 6、async、await的原理
>
> **Async 和 await 是一种同步的写法,但还是异步的操作,两个必须配合一起使用**
>
>
> **函数前面的`async`关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个`Promise`对象。**
>
>
> **await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西,如果是promise则会等待promaise 返回结果,接普通函数直接进行链式调用.**
>
>
> **await 能够获取promise执行的结果 await必须和async一起使用才行,async配合await使用是一个阻塞的异步方法**
>
>
> **如果await后面不是Promise对象, 就直接返回对应的值,只能在async函数中出现, 普通函数直接使用会报错**
>
>
> **await语句后的Promise对象变成reject状态时,那么整个async函数会中断,后面的程序不会继续执行**
>
>
>
**使用场景:**
>
> 我在项目中: 需求:执行第一步,将执行第一步的结果返回给第二步使用。在ajax中先拿到一个接口的返回数据,然后使用第一步返回的数据执行第 二步操作的接口调用,达到异步操作。
>
>
>
#### 7、解构赋值
>
> **ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值**
>
>
> **常见的几种方式有**
>
>
> 1.默认值
>
>
> 2.交换变量
>
>
> 3.将剩余数组赋给一个变量
>
>
> **结构数组和对象字符串区别**
>
>
> **对象的解构与数组类似,但有所不同。数组的元素是按次序排列的,变量的取值由它的位置决定;**
>
>
> **而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。字符串也是可以解构赋值的。字符串被转换成了一个类似数组的对象.**
>
>
> **我在项目中**:就是从目标对象或数组中提取自己想要的变量。最常用的场景是:element-ui,vant-ui按需引入,请求接口返回数据,提取想要数据。
>
>
>
#### 8、 for...in 迭代和 for...of 有什么区别
>
> 1、 推荐在循环对象属性的时候,使用 for...in,在遍历数组的时候的时候使用for...of。
>
>
> 2、 for in遍历的是数组的索引,而for of遍历的是数组元素值
>
>
> 3、for...of 不能循环普通的对象,需要通过和 Object.keys()搭配使用
>
>
> 4、for...in 遍历顺序以数字为先 无法遍历 symbol 属性 可以遍历到公有中可枚举的
>
>
> 5、从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。
>
>
>
#### 9、 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并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口…)
> promise编写代码相比Generator、async更为复杂化,且可读性也稍差
> Generator、async需要与promise对象搭配处理异步情况
> async实质是Generator的语法糖,相当于会自动执行Generator函数
> async使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案**
>
>
>
#### 10、js构造函数的静态成员和实例成员
>
> js的构造函数(在别的后台语言上叫做类)上可以添加一些成员,可以在构造函数内部的this上添加,可以在构造函数本身上添加,通过这两种方式添加的成员,就分别称为实例成员和静态成员
>
>
> ***实例成员:构造函数中this上添加的成员* 静态成员:构造函数本身上添加的成员**
>
>
> **实例成员,只能由实例化的对象来访问 静态成员,只能由构造函数本身来访问 实例化对象的proto指向构造函数的prototype属性指向的对象,实例化的对象可以访问到它后者身上的成员**
>
>
>
**构造函数生成实例的执行过程:使用面向对象编程时,new关键字做了什么?**
>
> 1. 新建了一个Object对象
> 2. 修改构造函数this的指向,是其指向新建的Object对象,并且执行构造函数
> 3. 为Object对象添加了一个**proto**属性,是其指向构造函数的prototype属性
> 4. 将这个Object对象返回出去
>
>
>
#### 11、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, ‘哈哈’ => ‘嘻嘻嘻’ }
#### 12、proxy 的理解
>
> **Proxy** **对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。**
>
>
> Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
>
>
>
#### 13、Es6中新的数据类型symbol
>
> **symbol 是es6 加入的,是一个基本数据类型,它代表的是一个独一无二的值,SYMBOL 值是由 SYMBOL函数生成**,**也就是说现在我们定义对象的属性名字可以是原有的字符串 也可以是 symbol 类型的,symbol 可以保证不与其他属性名冲突,减少了bug的产生,**
>
>
> 如果那 symbol 对比的话 就是会返回 false
>
>
> symbol 他是一个原始类型的值就,**不可以使用 new 关键字,symbol不是对象 没有迭代器的接口 不能去添加属性值,他是类似于字符串的一种类型**
>
>
> **symbol 不能用来四则运算,否则会报错**,只能用显示的方式转为字符串
>
>
> symbol 参数里的 a 表示一种修饰符 对当前创建的 symbol 的一种修饰,作为区分 ,否则会混淆
>
>
>
#### 14、iterator == iteration (遍历器的概念)
>
> **遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作**
>
>
> Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令`for...of`循环,Iterator 接口主要供`for...of`消费。
>
>
> 其实iteration == iterator 有三个作用:
>
>
> 1. **为各种数据结构,提供一个统一的、简便的访问接口;**
> 2. **使得数据结构的成员能够按某种次序排列;**
> 3. **主要供`for...of`消费**
>
>
>
#### 15、Object.assign
>
> Object.assign可以实现对象的合并。它的语法是这样的: `Object.assign(target, ...sources)`
>
>
> `Object.assign`会将source里面的**可枚举属性**复制到`target`。如果和target的已有属性重名,则会覆盖。同时后续的source会覆盖前面的source的同名属性。
>
>
> Object.assign复制的是属性值,如果属性值是一个引用类型,那么复制的其实是引用地址,就会存在引用共享的问题
>
>
> `Array.from()`方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。
>
>
> 那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有length属性的对象。
>
>
>
**1、将类数组对象转换为真正数组:**
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、该类数组对象的属性名必须为数值型或字符串型的数字
>
>
>
#### 16、谈谈你对模块化开发的理解?
>
> **我对模块的理解是,一个模块是实现一个特定功能的一组方法**。在最开始的时候,js 只实现一些简单的功能,所以并没有模块的概念 ,但随着程序越来越复杂,代码的模块化开发变得越来越重要。
>
>
> 由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污 染,并且模块间没有联系。
>
>
> 后面提出了对象写法,**通过将函数作为一个对象的方法来实现**,**这样解决了直接使用函数作为模块的一些缺点**,但是这种办法会暴露所 有的所有的模块成员,外部代码可以修改内部属性的值。
>
>
> **现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。**
>
>
>
#### 17、js 的几种模块规范?
>
> js 中现在比较成熟的有四种模块加载方案:
>
>
> * **第一种是 CommonJS 方案**,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。
> * **第二种是 AMD 方案**,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。
> * **第三种是 CMD 方案**,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。
> * **第四种方案是 ES6 提出的方案,使用 import 和 export 的形式来导入导出模块。**
>
>
>
**加油快通关了,通关了你会有收获的。**
### vue面试题
### 核心原理部分
#### **mvc mvvm和mvp的区别**?
>
> 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 统一管理**,
>
>
>
> 整体看来,MVVM比MVC精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作DOM元素。因为在MVVM中,View不知道Model的存在,Model和ViewModel也观察不到View,这种低耦合模式提高代码的可重用性。
>
>
> 【优点】
>
>
> 数据源和视图实现了双向绑定,很好的做到了数据的一致性 相比于mvp各层的耦合度更低,一个viewmodel层可以给多个view层共用。
>
>
> 【缺点】
>
>
> 因为使用了dataBinding,增加了大量的内存开销,增加了程序的编译时间,项目越大内存开销越大。 数据绑定使得 Bug 很难被调试。**你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题**
>
>
>
> MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范
> - Model(模型):是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据
> - View(视图):是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的
> - Controller(控制器):是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据,也可以将Model的数据用View显示出来
>
>
>
> 【优点】
>
>
> 耦合性低,方便维护,可以利于分工协作 重用性高
>
>
> 【缺点】
>
>
> 使得项目架构变得复杂,对开发人员要求高
>
>
>
> MVP MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示,在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的
>
>
>
#### Vue底层实现原理
>
> **vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,**触发相应的监听回调 Vue是一个典型的MVVM框架,模型(Model)只是普通的javascript对象,修改它则-视图(View)会自动更新。这种设计让状态管理变得非常简单而直观
>
>
> **Observer(数据监听器)** : Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher
>
>
> **Compile(指令解析器)** : Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新-视图
>
>
> **Watcher(订阅者)** : Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:
>
>
> 1. 在自身实例化时往属性订阅器(dep)里面添加自己
> 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 操作,操作完后又会生成一颗 dom 树,两颗 DOM 树进行比较,根据 diff 算法比较两颗 DOM 树不同的地方,只渲染一次不同的地方。
>
>
> (个人理解)**虚拟dom他不并不是真实的 dom ,是根据模板生成一个js对象(使用createElement,方法),根据这个js对象再去生成真实的dom,对复杂的文档DOM结构,提供一种方便的工具,进行最小化的DOM操作 ,是可以快速的渲染和高效的更新元素,提高浏览器的性能,**
>
>
> 例如,一个 ul 标签下很多个 li 标签,其中只有一个 li 有变化,这种情况下如果使用新的 ul 去替代旧的 ul,因为这些不必要的 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 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。
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.
s
e
t
(
t
a
r
g
e
t
,
k
e
y
,
v
a
l
u
e
)
来解决 参数:
O
b
j
e
c
t
∣
A
r
r
a
y
t
a
r
g
e
t
s
t
r
i
n
g
∣
n
u
m
b
e
r
p
r
o
p
e
r
t
y
N
a
m
e
/
i
n
d
e
x
a
n
y
v
a
l
u
e
第一参数时指定要修改的数据(
t
a
r
g
e
t
) 第二个参数就是你要设置数据的下标或者是属性名 第三个参数就是现在要修改的数据(重新赋的值)改变
/
添加对象属性的时候
:
t
h
i
s
.
set( target, key, value ) 来解决 参数: {Object | Array} target {string | number} propertyName/index {any} value 第一参数时指定要修改的数据 (target) 第二个参数就是你要设置数据的下标或者是属性名 第三个参数就是现在要修改的数据 (重新赋的值) 改变/添加 对象属性的时候:this.
set(target,key,value)来解决 参数: Object∣Arraytarget string∣numberpropertyName/index anyvalue 第一参数时指定要修改的数据(target) 第二个参数就是你要设置数据的下标或者是属性名 第三个参数就是现在要修改的数据(重新赋的值)改变/添加对象属性的时候:this.set(data 实例,“属性名(添加的属性名)”,“属性值(添加的属性值)”)
改变/添加 数组属性的时候:this.$set(data 实例,数组下标,“改变后的元素(添加的元素)”)
原因 : vue在创建实例的时候把data深度遍历所有属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。让 Vue 追踪依赖,在属性被访问和修改时通知变化。所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
为什么要用 this.
s
e
t
呢?
t
h
i
s
.
set 呢? this.
set呢?this.set是干什么的?
当你发现你给对象加了一个属性,在控制台能打印出来,但是却没有更新到视图上时,也许这个时候就需要用到this.
s
e
t
()这个方法了,简单来说
t
h
i
s
.
set()这个方法了,简单来说this.
set()这个方法了,简单来说this.set的功能就是解决这个问题的啦。官方解释:向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty = ‘hi’),你会发现vue官网是vue.set,vue.set的用法
那 Vue.set 和 this.
s
e
t
有什么区别?
V
u
e
.
s
e
t
(
)
是将
s
e
t
函数绑定在
V
u
e
构造函数上,
t
h
i
s
.
set 有什么区别 ? Vue.set( ) 是将 set 函数绑定在 Vue 构造函数上,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的运行环境;
> * 更多的服务端负载。
>
>
>
#### vue3.0 与 vue2.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不需要
>
>
> 1. 双向数据绑定原理发生了改变,使用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
#### vue 与 react的区别
>
> **相同点 :**
>
>
> * 都是使用了虚拟dom
> * 组件化开发
> * 父子之间通信单项数据流
> * 都支持服务端渲染
>
>
> **不同点:**
>
>
> * reacct 的jsx vue的是 template
> * 数据变化,react 手动 setState vue自动响应式处理 proxy object.DefineProperty
> * react 单向数据流 ,vue双向数据流
> * react 的 redux mobx vue 的vuex 。pinia
>
>
>
### 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. n e x t T i c k ( ) 将回调延迟到下次 D O M 更新循环之后执行。在修改数据之后立即使用它,然后等待 D O M 更新。它跟全局方法 V u e . n e x t T i c k 一样,不同的是回调的 t h i s 自动绑定到调用它的实例上。可以根据打印的顺序看到,在 c r e a t e d ( ) 钩子函数执行的时候 D O M 其实并未进行任何渲染,而此时进行 D O M 操作并无作用,而在 c r e a t e d ( ) 里使用 t h i s . nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。 可以根据打印的顺序看到,在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作并无作用,而在created()里使用this. nextTick()将回调延迟到下次DOM更新循环之后执行。在修改数据之后立即使用它,然后等待DOM更新。它跟全局方法Vue.nextTick一样,不同的是回调的this自动绑定到调用它的实例上。可以根据打印的顺序看到,在created()钩子函数执行的时候DOM其实并未进行任何渲染,而此时进行DOM操作并无作用,而在created()里使用this.nextTick()可以等待dom生成以后再来获取dom对象,而通过this.KaTeX parse error: Expected '}', got 'EOF' at end of input: …nsole.log(this.refs.button);
});
3、那 setTimeout this.
n
e
x
t
T
i
c
k
什么区别呢?
s
e
t
T
i
m
e
o
u
t
将同步转换为异步
t
h
i
s
.
nextTick 什么区别呢? setTimeout 将同步转换为异步 this.
nextTick什么区别呢?setTimeout将同步转换为异步this.nextTick
this.
n
e
x
t
T
i
c
k
将回调延迟到下次
D
O
M
更新循环之后执行。在修改数据之后立即使用它,
4
、
t
h
i
s
.
nextTick 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它, 4、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 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好(内部采用数组的方式存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)
>
>
>
### vuex常问的考点
#### Vuex严格模式
开启严格模式,仅需在创建 store 的时候传入 strict: true:
const store = new Vuex.Store({
// …
strict: true
})
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
#### 开发环境与发布环境
**不要在发布环境下启用严格模式**!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
类似于插件,我们可以让构建工具来处理这种情况:
const store = createStore({
// …
strict: process.env.NODE_ENV !== ‘production’
})
#### vuex是什么, state,getters,mutations,actions,modules的用途和用法
>
> **vuex是一个状态管理工具,所谓状态的是就是数据,采用集中式存储管所有组件的状态,是为了结局中大型项目一个数据共享的问题。vuex 他可以将数据保存到本地,数据是响应式的,能够保持数据页面的共享,提高开发效率。**
>
>
>
**好处:**
>
> 能够在 vuex 中集中管理共享的数据,易于开发和后期维护 可以做状态管理、采用localstorage保存信息、数据一直存储在用户的客户端中 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步,能够高效地实现组件之间的数据共享,提高开发 效率
>
>
>
**vuex核心:**
>
> 1. state:vuex的基本数据,数据源存放地,用于定义共享的数据。
> 2. getter:从基本数据派生的数据,相当于state的计算属性
> 3. mutation:提交更新数据的方法,唯一 一个可以操作state 中数据的方法,必须是同步的,第一个参数是state,第二个参数是cmmi传过来的数据
> 4. action: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
>
>
> 1. 辅助函数可以把vuex中的数据和方法映射到vue组件中。达到简化操作的目的
> 2. 如何使用:
>
>
> 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、甚至是嵌套子模块。方便管理
>
>
>
### vue的路由
#### 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的方法 router/index.js
>
> **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`**: 即将要进入的目标 [路由对象](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)
* **`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项目中,界面通常由多个嵌套的组件构成, 必须先清楚这样一件事,一个<router-view/>对应展示的就是一个组件 因此实现嵌套路由有两个要点: 路由对象中定义子路由 用children实现嵌套路由 组件内<router-view/>的使用.
>
>
>
#### 路由配置:
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; // 路由别名
children: Array; // 嵌套子路由
// 路由单独钩子
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 获取
>
>
>
#### Vue-router共有几种模式?默认是那种?
>
> 有两种模式 hash和history模式 默认是hash
>
>
> 1、hash ——即地址栏 URL 中的#符号,它的特点在 于:hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影 响,因此改变 hash 不会重新加载页面。
>
>
> 2、history ——利用了 HTML5 History api 在浏览器中没有# 有浏览器兼容问题
>
>
> history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 地址后加上/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。
>
>
>
#### 路由懒加载
>
> 使用原因:在单页应用中,如果没有应用懒加载,运用 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 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。
>
>
>
### 指令部分进行扩展
#### 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-for 与 v-if 的优先级
>
> 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:指令的绑定值
>
>
>
补充 : vue3的自定义指令钩子函数?
created - 自定义指令所在组件, 创建后
beforeMount - 就是Vue2.x中的 bind, 自定义指令绑定到 DOM 后调用. 只调用一次, 注意: 只是加入进了DOM, 但是渲染没有完成
mounted - 就是Vue2.x中的 inserted, 自定义指令所在DOM, 插入到父 DOM 后调用, 渲染已完成(最最重要)
beforeUpdate - 自定义指令所在 DOM, 更新之前调用
updated - 就是Vue2.x中的 componentUpdated
beforeUnmount - 销毁前
unmounted - 销毁后
**自定义指令原理**
>
> 1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
>
>
> 2.通过 genDirectives 生成指令代码
>
>
> 3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子
>
>
> 4.当执行指令对应钩子函数时,调用对应指令定义的方法
>
>
>
### 选项对象和常用api
#### 什么是过滤器?怎么定义全局和局部过滤器
>
> 过滤器是对 即将显示的数据做进一步的筛选处理,然后显示,过滤器并没有改变原来的数据,只是在原数据的基础上产生新的数据
>
>
> 全局:
>
>
> Vue.filter(‘过滤器名’,funciton(val){})
>
>
> 局部过滤器,定义在组件内部 filters 属性上.它只能在此组件内部使用.
>
>
> filters:{过滤器名:funciton(参数){//逻辑代码}}
>
>
> 使用: 过滤时间,过滤金钱
>
>
>
#### 什么是 mixin ?
>
> Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。 mixin 项目变得复杂的时候,多个组件间有重复的逻辑就会用到mixin
>
>
> 如果希望在多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。
>
>
> 然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。
>
>
>
#### 在vue.js中mixin和页面执行顺序问题
>
> **mixin中的代码先执行,单文件中的后执行。**
>
>
> **mixin的beforeCreate** > **父beforeCreate** > **mixin的created** > **父created** > **mixin的beforeMount** > **父beforeMount** > **子beforeCreate** > **子created** > **子beforeMount** > **子mounted** > **mixin的mounted** >**父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 进行了合并 基础用法
>
>
>
// 创建构造器 /* Vue.extend( options ) 参数:{Object} options 用法:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象; data 选项是特例,需要注意: 在 Vue.extend() 中它必须是函数;*/ var Profile = Vue.extend({ template: '
{{firstName}} {{lastName}} aka {{alias}}
', data: function () { return { firstName: 'Walter', lastName: 'White', alias: 'Heisenberg' } } }) // 创建 Profile 实例,并挂载到一个元素上。 new Profile().$mount('#mount-point') // 结果如下:Walter White aka Heisenberg
/* 可以看到,extend 创建的是 Vue 构造器,而不是我们平时常写的组件实例,所以不可以通过 new Vue({ components: testExtend }) 来直接使用,需要通过 new Profile().$mount(’#mount-point’) 来挂载到指定的元素上。 */ ```组件模块部分(插槽,单页面,通信)
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是接受的数据})
**12种组件通信** [vue12种通信方式](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)
prop 验证,和默认值
props:会接收不同的数据类型,常用的数据类型的设置默认值的写法,Number, String, Boolean, Array, Function, Object
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级prop 的更新流动到子组件中,但是反过来则不行。这样防止子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。若果在子组件中直接修改prop传递的值,Vue会发出警告,
方法二:通过 vuex 实现 (要了解)
具体实现:vuex 是一个状态管理工具,主要解决大中型复杂项目的数据共享问题,主要包括 state,actions,mutations,getters 和 modules 5 个要素,主要流程:组件通过 dispatch 到 actions,actions 是异步操作,再 actions中通过 commit 到 mutations,mutations 再通过逻辑操作改变 state,从而同步到组件,更新其数据状态
组件中写 name 选项有什么作用
① 项目使用 keep-alive 时,可搭配组件的 name 进行缓存过滤。 ② DOM 做递归组件时需要调用自身 name ③ vue-devtools 调试工具里显示的组件名称是由 vue 中组件 name 决定的
自定义组件
在vue 中的 component 中新建组件,定义好视图层,
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 就是一个函数式组件
“高阶组件”——用于接收一个组件作为参数,返回一个被包装过的组件
vue的项目中问题
单页面应用和多页面应用区别以及优缺点,
单页面:只有一个html页面,跳转方式是组件之间的切换
优点:跳转流畅、组件化开发、组件可复用、开发便捷
缺点:首屏加载过慢
多页面:有多个页面,跳转方式是页面之间的跳转
优点:首屏加载块
缺点:跳转速度慢
为什么要使用脚手架
快速开始一个vue项目,不用手动配置,直接开发
口喷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 怎么兼容 IE
使用 babel-polyfill 插件,和前缀名 hack
Vue 怎么重置 data
使用 Object.assign(),vm.data 可 以 获 取 当 前 状 态 下 的 data ,
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-cli 替我们做了哪些工作
vue-cli 是基于 Vue.js 进行快速开发的完整系统,也可以理解成是很多 npm 包的集合。
vue-cli 完成的功能:
.vue 文件 --> .js 文件 ES6 语法 --> ES5 语法 Sass,Less,Stylus --> CSS 对 jpg,png,font 等静态资源的处理 热更新 定义环境变量,区分 dev 和 production 模式 如果开发者需要补充或修改默认设置,需要在 package.json 同级下新建一个 vue.config.js 文件
Vue 如何检测数组变化
数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择对 7 种数组**(push,shift,pop,splice,unshift,sort,reverse)**方法进行重写(AOP 切片思想)
所以在 Vue 中修改,数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新
vue 中使用了哪些设计模式
1.工厂模式 - 传入参数即可创建实例 虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode
2.单例模式 - 整个程序有且仅有一个实例 vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉
3.发布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略
如何解决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压缩等。防止内部泄漏,组件销毁后把全局变量和事件销毁
js的大山
使用面向对象编程时,new关键字做了什么?
- 新建了一个Object对象
- 修改构造函数this的指向,是其指向新建的Object对象,并且执行构造函数
- 为Object对象添加了一个proto属性,是其指向构造函数的prototype属性
- 将这个Object对象返回出去
es6和es5的继承(继承不用搞那么麻烦,项目中还是用 class)
原型链继承
父类的实例作为子类的原型,易于实现,父类的新增实例与属性子类都能访问,创建子类实例,不能向父类构造函数中传参数。
原型链继承
实现:
父类的实例作为子类的原型
可以在子类中增加实例属性,如果要新增加原型属性和方法需要在new 父类构造函数的后面
优点:
简单,易实现 父类的新增实例与属性子类都能访问
缺点:
无法实现多继承 创建子类实例时,不能向父类构造函数中传参数
function Person() {
this.a = true
}
Person.prototype.fn = function () {
console.log('我是父类')
}
function Son() {
this.b = false
}
这里是关键 创建 Person 的实例 然后赋值给 Son 的原型
Son.prototype = new Person()
let xiaoming = new Son()
xiaoming.fn()
console.log(xiaoming.a)
构造函数继承(伪造对象、经典继承)
不能继承原型属性/方法,可以实现多继承,可以传参,无法复用,
构造函数继承
实现:
在子类内,使用call()调用父类方法,并将父类的this修改为子类
的this.相当于是把父类的实例属性复制了一份放到子类的函数内.
优点:
解决了子类构造函数向父类构造函数中传递参数
可以实现多继承(call或者apply多个父类)
缺点:
方法都在构造函数中定义,无法复用
不能继承原型属性/方法,只能继承父类的实例属性和方法
function Father(age, name) {
this.a = '22'
}
function Children() {
Father.call(this)
}
let Class = new Children()
console.log(Class.a);
组合继承
通过call 对实例属性的继承,原型链对原型方法的继承, 调用多次
组合继承
实现:
组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,
借用构造函数技术来实现实例属性的继承。
缺点:
由于调用了两次父类,所以产生了两份实例
优点:
函数可以复用
不存在引用属性问题
可以继承属性和方法,并且可以继承原型的属性和方法
function Father(name) { // 这里的name 就是 Son 传过来的
this.name = name
this.colors = [1, 2]
}
Father.prototype.sayName = function () {
alert(this.name)
}
function Son(name, age) {
//继承实例属性,第一次调用Father()
Father.call(this, name) // 这里通过 call 传过去 name 继承实例属性
this.age = age
}
Son.prototype = new Father() // 继承父类方法,第二次调用 Father
Son.prototype.ff = function () { // 子类的方法
console.log('666');
}
var aa = new Son('小明', 5)
aa.colors.push(3)
console.log(aa); // 打印了 父类的属性 和方法 以及子类的方法
var bb = new Son('小红', 50)
aa.colors.push(999999)
console.log(bb); // 打印了 父类的属性 和方法 以及子类的方法
Es6有class继承:
首先利用class构造一个父类,然后利用extends与super实现子类继承
ES6类继承extends
extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法,使用例子如下。
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width;
}
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 输出 200
-----------------------------------------------------------------
// 继承
class Square extends Rectangle {
constructor(length) {
super(length, length);
// 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
}
const square = new Square(10);
console.log(square.area);
// 输出 100
ES5继承和ES6继承的区别:
es5继承首先是在子类中创建自己的this指向,最后将方法添加到this中
Child.prototype=new Parent() || Parent.apply(this) || Parent.call(this)
es6继承是使用class关键字先创建父类的实例对象this,最后在子类class中修改this
javascript原型与原型链
- 每个函数都有一个prototype属性,被称为显示原型
- 每个实例对象都会有
_ _proto_ _
属性,其被称为隐式原型- 每一个实例对象的隐式原型
_ _proto_ _
属性指向自身构造函数的显式原型prototype- 每个prototype原型都有一个constructor属性,指向它关联的构造函数。
原型链
获取对象属性时,如果对象本身没有这个属性,那就会去他的原型
__proto__
上去找,如果还查不到,就去找原型的原型,一直找到最 顶层(Object.prototype
)为止。Object.prototype对象也有proto属性值为null。链式查找机制叫原型链。
javascript 创建对象的几种方式
1、我们一般使用字面量的形式直接创建对象
(1)第一种是工厂模式,工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的。
(2)第二种是构造函数模式。js 中每一个函数都可以作为构造函数,只要一个函数是通过 new 来调用的,那么我们就可以把它称为构造函数。
(3)第三种模式是原型模式,因为每一个函数都有一个 prototype 属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法。
(4)第四种模式是组合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。
(5)第五种模式是动态原型模式,这一种模式将原型方法赋值的创建过程移动到了构造函数的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。
(6)第六种模式是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,
什么是设计模式?
概念:
设计模式****是一套被反复使用的代码,设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 设计模式让代码变得工程化,设计模式是软件工程的基石。
1、js工厂模式,去做同样的事情,实现同样的效果,解决多个相似的问题这时候需要使用工厂模式
2、发布订阅模式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
3、单例模式 单例模式 保证一个类仅有一个实例,并提供一个访问它的全局访问点
constructor,proto,prototype的三角关系。
构造函数的prototype指向原型对象
实例对象的proto指向构造函数的prototype所指向原型对象
原型对象的constructor指向构造函数
面向过程,面向对象,面向过程和面向对象的优缺点
一、面向过程:面向过程就是分析出实现需求所需要的步骤,通过函数一步一步实现这些步骤,接着依次调用即可。
二、面向对象:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程面向过程:
优点:性能上它是优于面向对象的,因为类在调用的时候需要实例化,开销过大。
缺点:不易维护、复用、扩展
用途:单片机、嵌入式开发、Linux/Unix等对性能要求较高的地方
面向对象:
面向对象有三大特性:封装,继承,多态。
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 。
缺点:性能比面向过程低
扩展面试题
spa
spa 就是我们的单页面应用,spa 应用就是只有一个html页面,在vue中可以通过vue-router 来进行页面的切换的,而非刷新整个页面,可以实现无刷新切换页面的技术
SPA的原理?
通过这一点可以用js监听url中hash值的变化 onhashchange 事件在当前 URL 的锚部分(以 ‘#’ 号为开始) 发生改变时触发 。哈希值的变换并不会引发页面的刷新和跳转,当监听到hash变化,就可以动态的切换组件,就可以实现无刷新切换页面技术spa 的优点?
页面切换快:
页面每次切换跳转时,并不需要做html
文件的请求,这样就节约了很多http
发送时延,我们在切换页面的时候速度很快。
用户体验好:
页面片段间的切换快,在网络环境差的时候, 因为组件已经预先加载好了, 并不需要发送网络请求, 所以用户体验好
转场动画spa 的缺点?
首屏加载比较慢因为要请求一次html同时还要发送一次js请求,两次请求回来了首屏才会显示
不利于SEO
seo 效果较差 因为搜索引擎只识别html里面的内容,并不识别js里的内容,因为单页面就是js渲染出来的,影响网站的排名
mpa
MPA多页面应用程序 指的就是有多个独立的html页面,每个页面必须重复加载html js css 资源,多页面跳转需要整个页面资源刷新。
优点
1、首屏加载速度快
当我们访问页面的时候,服务器只返回了一个html,页面就展示出来了,只发了一次http请求,所以页面显示非常快.
2、SEO效果好
因为搜索引擎在做网站排名的时候,要根据网页的内容给网页排名,搜素引擎只可以识别html内容,多页面就是将内容放在html中,所以排名要好一点。缺点
因为每跳转一个页面都要发送一次http请求,如果网络情况不好的情况下,页面之间来回跳转就会发生明显的卡顿,有的时候半天还加载不出来,影响用户体验。转场动画也不好实现
1、 Vue与Angular以及React的区别
▍Angular
框架比较成熟完整,过于庞大,上手难;
指令以ng-xxx开头; 由谷歌开发和维护;
版本1比较适合PC端开发,版本2在往移动端靠;
不支持低版本浏览器; 内置指令和自定义指令;
内置过滤器和自定义过滤器; 支持双向数据绑定;
▍Vue
它是一个轻量级框架,其核心库只关注视图层,简单小巧、易学易上手;
指令以v-xxx开头; 个人维护项目; 适合于移动端开发; 不支持低版本浏览器;
内置指令和自定义指令; 内置过滤器和自定义过滤器; 支持双向数据绑定;
使用DOM模板。中心思想:一切都是组件,组件实例之间可以嵌套; 核心库不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载; 基于依赖追踪的观察系统,并且异步队列更新。
▍React
依赖虚拟DOM; 采用特殊的JSX语法; 中心思想:一切都是组件,组件实例之间可以嵌套; 核心库不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载。
2、 请描述一下你对webpack的理解?
Webpack Webpack 是一个项目打包工具
可以压缩代码和图片,把浏览器识别不了的代码转化为能识别的,可以启动一个热加载服务器
配置跨域、路径别名、打包分析、cdn映入、去掉console.log、单独打包第三方模块、ie兼容、eslint规范、图片压缩
3、vue2对比vue3
最大的区别就是: Vue2使用 选项类型API(Options API) 对比Vue3 合成型API(Composition API)
- 双向数据绑定原理发生了改变,使用proxy替换Object.defineProerty,使用Proxy的优势:
- 可直接监听数组类型的数据变
- 监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升
- 可直接实现对象属性的新增/删除
- 默认使用懒加载
在2.x版本里。不管数据多大,都会在一开始就为其创建观察者,在数据很大时,就会造成性能的问题。在3.x中,只会对渲染出来的数据创建观察者,而且3.x的观察者更高效。
3.0新加入了TypeScript以及PWA支持
生命周期有了一定的区别
Vue2--------------vue3
beforeCreate -> setup() 开始创建组件之前,创建的是data和method
created -> setup()
beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted -> onMounted 组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
updated -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。
destroyed -> onUnmounted 组件卸载之前执行的函数。
activated -> onActivated 组件卸载完成后执行的函数
deactivated -> onDeactivated
4、git命令
git init 初始化git仓库 (mac中Command+Shift+. 可以显示隐藏文件)
git status 查看文件状态
git add 文件列表 追踪文件
git commit -m 提交信息 向仓库中提交代码
git log 查看提交记录
1.分支明细
(1)主分支(master):第一次向 git 仓库中提交更新记录时自动产生的一个分支。
(2)开发分支(develop):作为开发的分支,基于 master 分支创建。
(3)功能分支(feature):作为开发具体功能的分支,基于开发分支创建
2.分支命令
(1)git branch 查看分支
(2)git branch 分支名称 创建分支
(3)git checkout 分支名称 切换分支
(4)git merge 来源分支 合并分支 (备注:必须在master分支上才能合并develop分支)
(5)git branch -d 分支名称 删除分支(分支被合并后才允许删除)(-D 强制删除)
3.暂时保存更改
(1)存储临时改动:git stash
(2)恢复改动:git stash pop
更加详细请看git常用命令
git怎么解决多人冲突?:
是当前修改是左箭头方向,传入的是右箭头的方向,
中间用等于号分割,等号上边是当前修改(本地),下边是传入的修改(线上的代码)。
两人同时提交可能会出现冲突,解决办法是手动修改冲突
5、前端有哪些页面优化方法?
- 减少 HTTP请求数
- 从设计实现层面简化页面
- 合理设置 HTTP缓存
- 资源合并与压缩
- 合并 CSS图片,减少请求数的又一个好办法。
- 将外部脚本置底(将脚本内容在页面信息内容加载后再加载)
- 多图片网页使用图片懒加载。
- 在js中尽量减少闭包的使用
- 尽量合并css和js文件
- 尽量使用字体图标或者SVG图标,来代替传统的PNG等格式的图片
- 减少对DOM的操作
- 在JS中避免“嵌套循环”和 “死循环”
- 尽可能使用事件委托(事件代理)来处理事件绑定的操作
- 浏览器缓存
- 防抖、节流
- 资源懒加载、预加载
- 开启Nginx gzip压缩
三个方面来说明前端性能优化
一: webapck优化与开启gzip压缩
1.babel-loader用 include 或 exclude 来帮我们避免不必要的转译,不转译node_moudules中的js文件
其次在缓存当前转译的js文件,设置loader: 'babel-loader?cacheDirectory=true'
2.文件采用按需加载等等
3.具体的做法非常简单,只需要你在你的 request headers 中加上这么一句:
accept-encoding:gzip
4.图片优化,采用svg图片或者字体图标
5.浏览器缓存机制,它又分为强缓存和协商缓存
二:本地存储——从 Cookie 到 Web Storage、IndexedDB
说明一下SessionStorage和localStorage还有cookie的区别和优缺点
三:代码优化
1.事件代理
2.事件的节流和防抖
3.页面的回流和重绘
4.EventLoop事件循环机制
5.代码优化等等
node,网络
1、什么是axios
基于promise的http库,可以用在浏览器和node.js,支持promiseAPI,客户端支持防御xsrf
2、Node是什么(别看这么简单,有的人一问就懵)
Node是一个基于Chrome V8引擎的JavaScript代码运行环境。
浏览器(软件)能够运行JavaScript代码,浏览器就是JavaScript代码的运行环境
Node(软件)能够运行JavaScript代码,Node就是JavaScript代码的运行环境
3、模块化的意义
一句话:降低软件的复杂性。使其可控,可维护,可扩展。
一个功能就是一个模板,多个模板可以组成完整应用,抽离一个模板不会影响其他功能的运行
4、网站的组成
网站应用程序主要分为两大部分:客户端和服务器端。客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使用HTML、CSS、JavaScript构建。服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑。
5、为什么要用node
简单强大,轻量可扩展。
简单体现在node使用的是javascript,json来进行编码,强大体现在非阻塞IO,可以适应分块传输数据,较慢的网络环境,尤其擅长高并发访问,轻量体现在node本身既是代码,又是服务器,前后端使用统一语言;可扩展体现在可以轻松应对多实例,多服务器架构,同时有海量的第三方应用组件。
6、node中的异步和同步怎么理解?
node是单线程的,异步是通过一次次的循环事件队列来实现的.同步则是说阻塞式的IO,这在高并发环境会是一个很大的性能问题,所以同步一般只在基础框架的启动时使用,用来加载配置文件,初始化程序什么的.
7、什么是npm?Npm的使用场景?
NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题。
使用场景:
a. 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
React
-
介绍一下react
-
React单项数据流
-
react生命周期函数和react组件的生命周期
-
react和Vue的原理,区别,亮点,作用
-
reactJs的组件交流
-
有了解过react的虚拟DOM吗,虚拟DOM是怎么对比的呢
-
项目里用到了react,为什么要选择react,react有哪些好处
-
怎么获取真正的dom
-
选择react的原因
-
react的生命周期函数
-
setState之后的流程
-
react高阶组件知道吗?
-
React的jsx,函数式编程
-
react的组件是通过什么去判断是否刷新的
-
如何配置React-Router
-
路由的动态加载模块
-
Redux中间件是什么东西,接受几个参数
-
redux请求中间件如何处理并发
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
t 组件挂载到节点上之前执行的函数。
destroyed -> onUnmounted 组件卸载之前执行的函数。
activated -> onActivated 组件卸载完成后执行的函数
deactivated -> onDeactivated
4、git命令
git init 初始化git仓库 (mac中Command+Shift+. 可以显示隐藏文件)
git status 查看文件状态
git add 文件列表 追踪文件
git commit -m 提交信息 向仓库中提交代码
git log 查看提交记录
1.分支明细
(1)主分支(master):第一次向 git 仓库中提交更新记录时自动产生的一个分支。
(2)开发分支(develop):作为开发的分支,基于 master 分支创建。
(3)功能分支(feature):作为开发具体功能的分支,基于开发分支创建
2.分支命令
(1)git branch 查看分支
(2)git branch 分支名称 创建分支
(3)git checkout 分支名称 切换分支
(4)git merge 来源分支 合并分支 (备注:必须在master分支上才能合并develop分支)
(5)git branch -d 分支名称 删除分支(分支被合并后才允许删除)(-D 强制删除)
3.暂时保存更改
(1)存储临时改动:git stash
(2)恢复改动:git stash pop
更加详细请看git常用命令
git怎么解决多人冲突?:
是当前修改是左箭头方向,传入的是右箭头的方向,
中间用等于号分割,等号上边是当前修改(本地),下边是传入的修改(线上的代码)。
两人同时提交可能会出现冲突,解决办法是手动修改冲突
5、前端有哪些页面优化方法?
- 减少 HTTP请求数
- 从设计实现层面简化页面
- 合理设置 HTTP缓存
- 资源合并与压缩
- 合并 CSS图片,减少请求数的又一个好办法。
- 将外部脚本置底(将脚本内容在页面信息内容加载后再加载)
- 多图片网页使用图片懒加载。
- 在js中尽量减少闭包的使用
- 尽量合并css和js文件
- 尽量使用字体图标或者SVG图标,来代替传统的PNG等格式的图片
- 减少对DOM的操作
- 在JS中避免“嵌套循环”和 “死循环”
- 尽可能使用事件委托(事件代理)来处理事件绑定的操作
- 浏览器缓存
- 防抖、节流
- 资源懒加载、预加载
- 开启Nginx gzip压缩
三个方面来说明前端性能优化
一: webapck优化与开启gzip压缩
1.babel-loader用 include 或 exclude 来帮我们避免不必要的转译,不转译node_moudules中的js文件
其次在缓存当前转译的js文件,设置loader: 'babel-loader?cacheDirectory=true'
2.文件采用按需加载等等
3.具体的做法非常简单,只需要你在你的 request headers 中加上这么一句:
accept-encoding:gzip
4.图片优化,采用svg图片或者字体图标
5.浏览器缓存机制,它又分为强缓存和协商缓存
二:本地存储——从 Cookie 到 Web Storage、IndexedDB
说明一下SessionStorage和localStorage还有cookie的区别和优缺点
三:代码优化
1.事件代理
2.事件的节流和防抖
3.页面的回流和重绘
4.EventLoop事件循环机制
5.代码优化等等
node,网络
1、什么是axios
基于promise的http库,可以用在浏览器和node.js,支持promiseAPI,客户端支持防御xsrf
2、Node是什么(别看这么简单,有的人一问就懵)
Node是一个基于Chrome V8引擎的JavaScript代码运行环境。
浏览器(软件)能够运行JavaScript代码,浏览器就是JavaScript代码的运行环境
Node(软件)能够运行JavaScript代码,Node就是JavaScript代码的运行环境
3、模块化的意义
一句话:降低软件的复杂性。使其可控,可维护,可扩展。
一个功能就是一个模板,多个模板可以组成完整应用,抽离一个模板不会影响其他功能的运行
4、网站的组成
网站应用程序主要分为两大部分:客户端和服务器端。客户端:在浏览器中运行的部分,就是用户看到并与之交互的界面程序。使用HTML、CSS、JavaScript构建。服务器端:在服务器中运行的部分,负责存储数据和处理应用逻辑。
5、为什么要用node
简单强大,轻量可扩展。
简单体现在node使用的是javascript,json来进行编码,强大体现在非阻塞IO,可以适应分块传输数据,较慢的网络环境,尤其擅长高并发访问,轻量体现在node本身既是代码,又是服务器,前后端使用统一语言;可扩展体现在可以轻松应对多实例,多服务器架构,同时有海量的第三方应用组件。
6、node中的异步和同步怎么理解?
node是单线程的,异步是通过一次次的循环事件队列来实现的.同步则是说阻塞式的IO,这在高并发环境会是一个很大的性能问题,所以同步一般只在基础框架的启动时使用,用来加载配置文件,初始化程序什么的.
7、什么是npm?Npm的使用场景?
NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题。
使用场景:
a. 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
React
-
介绍一下react
-
React单项数据流
-
react生命周期函数和react组件的生命周期
-
react和Vue的原理,区别,亮点,作用
-
reactJs的组件交流
-
有了解过react的虚拟DOM吗,虚拟DOM是怎么对比的呢
-
项目里用到了react,为什么要选择react,react有哪些好处
-
怎么获取真正的dom
-
选择react的原因
-
react的生命周期函数
-
setState之后的流程
-
react高阶组件知道吗?
-
React的jsx,函数式编程
-
react的组件是通过什么去判断是否刷新的
-
如何配置React-Router
-
路由的动态加载模块
-
Redux中间件是什么东西,接受几个参数
-
redux请求中间件如何处理并发