目录
7、为什么把css文件放head中,把js文件放body底下
-
HTML 、web综合
1、<!DOCTYPE html>的作用
doctype是一种标准通用标记语言的文档类型声明,目的是告诉标准通用标记语言解析器要使用什么样的文档类型定义(DTD)来解析文档。
声明是用来指示web浏览器关于页面使用哪个HTML版本进行编写的指令。 声明必须是HTML文档的第一行,位于html标签之前。浏览器本身分为两种模式,一种是标准模式,一种是怪异模式,浏览器通过doctype来区分这两种模式,doctype在html中的作用就是触发浏览器的标准模式,如果html中省略了doctype,浏览器就会进入到Quirks模式的怪异状态,在这种模式下,有些样式会和标准模式存在差异,而html标准和dom标准值规定了标准模式下的行为,没有对怪异模式做出规定,因此不同浏览器在怪异模式下的处理也是不同的,所以一定要在html开头使用doctype。
2、什么是web语义化,有什么好处?
web语义化是指通过HTML标记表示页面包含的信息,包含了HTML标签的语义化和css命名的语义化。 HTML标签的语义化是指:通过使用包含语义的标签(如h1-h6)恰当地表示文档结构 css命名的语义化是指:为html标签添加有意义的class,id补充未表达的语义,如Microformat通过添加符合规则的class描述信息 为什么需要语义化:
(1)去掉样式后页面呈现清晰的结构
(2)盲人使用读屏器更好地阅读
(3)搜索引擎更好地理解页面,有利于收录
(4)便团队项目的可持续运作及维护
3、浏览器内核
IE: trident内核
Firefox:gecko内核
Safari:webkit内核
Opera:以前是presto内核,Opera现已改用Google Chrome的Blink内核
Chrome:Blink(基于webkit,Google与Opera Software共同开发)
3、什么是DOM?
DOM(Document Object Model)的缩写,即文档对象模型。是针对XML并经过扩展用于HTML的应用程序编程接口(API),所以DOM本质上是一种接口(API),是专门操作网页内容的API标准
DOM把整个页面映射为一个多层节点结构,HTML或XML页面中的每个组成部分都是某种类型的节点。借助DOM提供的API,开发人员可以删除、添加、替换或修改任何节点。
4、什么叫优雅降级和渐进增强?
渐进增强 progressive enhancement:
针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
优雅降级 graceful degradation:
一 开始就构建完整的功能,然后再针对低版本浏览器进行兼容。
区别:
a. 优雅降级是从复杂的现状开始,并试图减少用户体验的供给
b. 渐进增强则是从一个非常基础的,能够起作用的版本开始,并不断扩充,以适应未来环境的需
c. 降级(功能衰减)意味着往回看;而渐进增强则意味着朝前看,同时保证其根基处于安全地带
5、什么是跨域?解决跨域的办法有哪些?
(1)同源策略
-是浏览器安全策略
- 协议名、域名、端口号必须完全一致
(2)跨域
违背同源策略就会产生跨域(由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一个与当前页面地址不同即为跨域)
(3)解决方法
jsonp(利用script标签不受同源策略的限制,天然可以跨域的特性) cors postMessage hash//jsonp实现跨域 <script type='text/javascript'> var script = document.createElement("script") //设置回调函数 function getData(data) { console.log(data); } script.src = "http://xxx?callback=getData"; document.getElementsByTagName('head')[0].appendChild(script) </script>
6、从一个url地址到最终页面渲染完成,发生了什么?
1、DNS解析:将域名地址解析为ip地址
- 浏览器DNS缓存
- 系统DNS缓存
- 路由器DNS缓存
- 网络运营商DNS缓存
- 递归搜索:blog.baidu.com
- .com域名下查找DNS解析
- .baidu域名下查找DNS解析
- blog域名下查找DNS解析
- 出错了
2、TCP连接:TCP三次握手
- 第一次握手,由浏览器发起,告诉服务器我要发送请求了
- 第二次握手,由服务器发起,告诉浏览器我准备接受了,你赶紧发送吧
- 第三次握手,由浏览器发送,告诉服务器,我马上就发了,准备接受吧
3、发送请求
- 请求报文:Http协议的通信内容
4、接收响应
- 响应报文
5、渲染页面
- 遇见HTML标记,浏览器调用HTML解析器解析成Token并构建成dom树
- 遇见style/link标记,浏览器调用css解析器,处理css标记并构建cssom树
- 遇见script标记,调用javascript解析器,处理script代码(绑定事件,修改dom树/cssom树)
- 将dom树和cssom树合并成一个渲染树
- 根据渲染树来计算布局,计算每个节点的几何信息(布局)
- 将各个节点颜色绘制到屏幕上(渲染)
注意:
这五个步骤不一定按照顺序执行,如果dom树或cssom树被修改了,可能会执行多次布局和渲染
往往实际页面中,这些步骤都会执行多次的。
6、断开连接:TCP四次挥手
- 第一次挥手:由浏览器发起的,发送给服务器,我东西发送完了(请求报文),你准备关闭吧
- 第二次挥手:由服务器发起的,告诉浏览器,我东西接收完了(请求报文,我准备关闭了,你也准备吧)
- 第三次挥手:由服务器发起的,告诉浏览器,我东西发送完了(响应报文),你准备关闭吧
- 第四次挥手:由浏览器发起的,告诉服务器,我东西接受完了,我准备关闭了(响应报文)
7、HTTP状态码
- 200 - 请求成功
- 301 - 资源(网页等)被永久转移到其它URL
- 404 - 请求的资源(网页等)不存在
- 500 - 内部服务器错误
8、Http协议的特点:
1.支持客户/服务器模式。
2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
3.灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type(Content-Type是HTTP包中用来表示内容类型的标识)加以标记。
4.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
5.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
9、前端性能优化
10、html5新特性
(1) 新的语义标签和属性
(2) 表单新特性
(3) 视频和音频
(4) Canvas绘图
(5) SVG绘图
(6) 地理定位
(7) 拖放API
11、请描述一下 cookies,sessionStorage 和 localStorage 的区别?
cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。
cookie数据始终在同源的http请求中携带(即使不需要),记会在浏览器和服务器间来回传递。
sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。存储大小:
cookie数据大小不能超过4k。
sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。有期时间:
localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;
sessionStorage 数据在当前浏览器窗口关闭后自动删除。
cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
12、事件的委派
* - 指将事件统一绑定给元素的共同的祖先元素,这样当后代元素上的事件触发时,会一直冒泡到祖先元素从而通过祖先元素的响应函数来处理事件。
* - 事件委派是利用了冒泡,通过委派可以减少事件绑定的次数,提高程序的性能
13、POST和GET的区别
(1) 对参数的数据类型,GET只接受ASCII字符,而POST没有限制,允许二进制。
(2) GET在浏览器回退/刷新时是无害的,而POST会再次提交请求。
(3) GET请求只能进行url编码(application/x-www-form-urlencoded),而POST支持多种编码方式(application/x-www-form-urlencoded 或 multipart/form-data),可以为二进制使用多重编码。
(4) POST 比 GET 更安全,因为GET参数直接暴露在URL上,POST参数在HTTP消息主体中,而且不会被保存在浏览器历史或 web 服务器日志中。
(5) 对参数数据长度的限制,GET方法URL的长度是受限制的,最大是2048个字符,POST参数数据是没有限制的。
(6) GET请求会被浏览器主动缓存,POST不会,除非手动设置。
(7) GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
待续...
-
CSS
1、什么是盒子模型?
在网页中,一个元素占有空间的大小由几个部分构成,其中包括元素的内容(content),元素的内边距(padding),元素的边框(border),元素的外边距(margin)四个部分。这四个部分占有的空间中,有的部分可以显示相应的内容,而有的部分只用来分隔相邻的区域或区域。4个部分一起构成了css中元素的盒模型。
2、使用纯CSS画一个三角形
#box {
width: 0px;
height: 0px;
border: 100px solid transparent;
border-top-color: deeppink;
border-left-color: deeppink;
}
3、你对BFC的理解是什么?
BFC直译为“块级格式化上下文”。它是一个独立渲染区域,只有块级盒子参与,它规定了内部的块级盒子如何布局,并且与这个区域外部毫不相干。
BFC布局规则:
(1)内部的Box会在垂直方向,一个接一个地放置。
(2)BFC的区域不会和float box重叠。
(3)内部的Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻的margin会发生重叠
(4)计算BFC的高度时,浮动元素也参与计算。(清除浮动 haslayout)
(5)BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之也如此。
BFC什么时候会出现(哪些元素会生成BFC?)
(1)根元素
(2)float属性不为none
(3)position为absolute或fixed
(4)overflow不为visible
(5)display为inline-block,table-cell,table-caption,flex,inline-flex
BFC可以做什么
(1)利用BFC避免外边距重叠
(2)利用BFC防止高度塌陷(计算BFC的高度时,浮动元素也参与计算)
(3)使用BFC避免文字环绕
4、元素垂直水平居中的方案有哪些?
5、圣杯布局和双飞翼布局
6、inline-block空白间隙怎么去除
7、为什么把css文件放head中,把js文件放body底下
因为浏览器解析html文档是从上到下的,解析完文档会先生成DOM树,同时解析CSS文档形成style rules,最后通过attachment将样式应用到DOM节点上形成渲染树。
css文件放头部的好处就是能在解析dom树时就把样式应用到相应的节点上,如果不放头部,页面可能会先加载出没有样式的html,再闪一下变成有样式的,用户体验不好。js文件的下载和执行会阻塞文档解析,现在的浏览器为了提高用户体验,渲染引擎尝试尽快在页面上显示内容,并不会等整个文档加载完后才开始绘制,如果把js文件放头部,会直接影响dom的解析,从而使第一次绘制的时间延后,页面留白时间增加,影响用户体验。并且js可能会有对dom的读取和操作,可能会undefined或报错。
【css不会阻塞dom的解析,但会阻塞dom的渲染,如果把css放底部,要等dom解析完后才加载css,这个时间就浪费了,也导致页面留白时间增加。】
8、display: none;
与visibility: hidden;
的区别
联系:它们都能让元素不可见
区别:
- display:none;会让元素完全从渲染树中消失,渲染的时候不占据任何空间;visibility: hidden;不会让元素从渲染树消失,渲染时元素继续占据空间,只是内容不可见
- display: none;是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示;visibility: hidden;是继承属性,子孙节点消失由于继承了hidden,通过设置visibility: visible;可以让子孙节点显式
- 修改常规流中元素的display通常会造成文档重排。修改visibility属性只会造成本元素的重绘。
- 读屏器不会读取display: none;元素内容;会读取visibility: hidden;元素内容
9、块元素和行内元素区别
(1)占位大小和宽度填充。块元素独占一行,宽度自动为父元素100%,行内元素只占本身的大小,宽度默认被内容撑开
(2)块元素能设置宽高。行内元素不能设置宽高,即使设置了也无效。
(3)margin和padding。块元素都有效,行内元素只在水平方向有效。
(4)元素包含。一般来说,块元素可以包含行内元素和块元素,行内元素不能包含块元素。特例:p不能包含任何块元素包括他自己,a可以包含任意元素除了他自己。
(5)display对应值
(6)常见元素举例。div,p,h,ul,li,table------span,a,img,input
10、回流与重绘
当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建, 这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。
当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。
注意:回流必将引起重绘,而重绘不一定会引起回流。
回流何时发生:
当页面布局和几何属性改变时就需要回流。下述情况会发生浏览器回流:
1、添加或者删除可见的DOM元素;
2、元素位置改变;
3、元素尺寸改变——边距、填充、边框、宽度和高度
4、内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;
5、页面渲染初始化;
6、浏览器窗口尺寸改变——resize事件发生时;
如何减少回流、重绘
- 直接改变className
- 让要操作的元素进行”离线处理”,处理完后一起更新
- 不要经常访问会引起浏览器flush队列的属性,如果你确实要访问,利用缓存
-
JavaScript
1、你对闭包的理解
理解:什么是闭包? 1. 密闭的容器,类似于set,map容器,存储数据的 2. 闭包是一个对象,存放数据的格式: key: value 形成的条件: 1. 函数嵌套 2. 内部函数引用外部函数的局部变量 生命周期: 1. 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用) 2. 死亡: 在嵌套的内部函数成为垃圾对象时 闭包的优点: 延长外部函数局部变量的生命周期 闭包的缺点: 容易造成内存泄漏 注意点: 1. 合理的使用闭包 2. 用完闭包要及时清除(销毁)
闭包经典面试题,输出的内容是什么?
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n)
}
}
}
var a = fun(0)
a.fun(1)
a.fun(2)
a.fun(3)//undefined,0,0,0
var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2
var c = fun(0).fun(1)
c.fun(2)
c.fun(3)//undefined,0,1,1
2、原型和原型链
原型:js中的每个构造函数都有一个prototype属性,指向该函数的原型对象,原型对象的constructor指向该构造函数;
可以给原型对象添加方法和属性,供该构造函数的实例使用;原型链:每个实例(通过new构造函数创建以及原型对象)都有一个_proto_隐式原型,指向实例的构造函数的原型对象;
原型对象的_proto_也有原型对象,一直到null,也就是Object.prototype.proro,就到达了原型链的终点;
当我们想要用到实例的某个属性或方法时,js会沿着_proto_一直向找,直到找到null,这就是原型链
3、继承的方式
(1)原型链继承
//缺点:实例对象共用父类的引用数据类型,一个实例对象修改了,其他的实例对象上访问到的数据也相应的改变了
function Supper() { //父类型
this.superProp = 'The super prop'
this.play = [1,2,3]
}
//原型的数据所有的实例对象都可见
Supper.prototype.showSupperProp = function () {
console.log(this.superProp)
}
function Sub() { //子类型
this.subProp = 'The sub prop'
}
// 子类的原型为父类的实例
Sub.prototype = new Supper()
// 修正Sub.prototype.constructor为Sub本身
Sub.prototype.constructor = Sub
Sub.prototype.showSubProp = function () {
console.log(this.subProp)
}
// 创建子类型的实例
var sub = new Sub()
// 调用父类型的方法
sub.showSubProp()
// 调用子类型的方法
sub.showSupperProp()
(2)借用构造函数(call()/apply())继承
//缺点:父类原型链上的东西并没有被子类继承
function Person(name, age) {
this.name = name
this.age = age
}
function Student(name, age, price) {
Person.call(this, name, age) // this.Person(name, age)
this.price = price
}
var s = new Student('Tom', 20, 12000)
console.log(s.name, s.age, s.price)
(3)组合方式(prototype和call()/apply()结合)继承
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}
function Student(name, age, price) {
Person.call(this, name, age) //得到父类型的属性
this.price = price
}
Student.prototype = new Person() //得到父类型的方法
Student.prototype.constructor = Student
Student.prototype.setPrice = function (price) {
this.price = price
}
(4)寄生组合继承
/*组合继承有个缺点,父类的构造函数会被调用两次.
1、子类构造函数中(Person.call),调用了第一次
2、new Person()实例化对象的时候,又调用一次
1的目的是为了复制属性,肯定是不能少的,2的目的是为了获取到父类原型对象(prototype)上的方法,基于这个目的,有没有别的方法 */
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.setName = function (name) {
this.name = name
}
function Student(name, age, price) {
Person.call(this, name, age) //得到父类型的属性
this.price = price
}
//Student.prototype = new Person() //得到父类型的方法
// 下面这部分替代给子类原型赋值的过程,不调用父类构造函数,直接继承父类原型
Student.prototype = Object.create(SuperType.prototype)
Student.prototype.constructor = Student
Student.prototype.setPrice = function (price) {
this.price = price
}
/*
注:
Object.create()方法是ES5中原型式继承的规范化。
在只传一个参数时,内部行为如下:
*/
function object (o) {
function F() {};
F.prototype = o;
return new F();
}
(5)对象冒充
function Person(name,age){
this.name = name;
this.age = age;
this.show = function(){
console.log(this.name+", "+this.age);
}
}
function Student(name,age){
this.student = Person; //将Person类的构造函数赋值给this.student
this.student(name,age); //js中实际上是通过对象冒充来实现继承的
delete this.student; //移除对Person的引用
}
var s = new Student("小明",17);
s.show();
var p = new Person("小花",18);
p.show();
// 小明, 17
// 小花, 18
4、深度克隆
- 基本数据类型存放的就是实际的数据,可直接复制
let number2 = 2;
let number1 = number2;
- 克隆数据:对象/数组
1、区别: 浅拷贝/深度拷贝
判断: 拷贝是否产生了新的数据还是拷贝的是数据的引用
知识点:对象数据存放的是对象在栈内存的引用,直接复制的是对象的引用
let obj = {username: 'kobe'}
let obj1 = obj; // obj1 复制了obj在栈内存的引用
2、常用的拷贝技术
1). arr.concat(): 数组浅拷贝
2). arr.slice(): 数组浅拷贝
3). JSON.parse(JSON.stringify(arr/obj)): 数组或对象深拷贝, 但不能处理函数数据
4). 浅拷贝包含函数数据的对象/数组
5). 深拷贝包含函数数据的对象/数组
封装一个函数实现数据的深度克隆(深拷贝)
function deepClone(target){
let result,type = Object.prototype.toString.call(target).slice(8, -1)
if(type === 'Object'){
result = {}
}else if(type === 'Array'){
result = []
}else{ //如果是基本数据类型直接拷贝返回
return target
}
for(let key in target){
//如果对象或数组里面又存在对象或数组,则递归调用
if(Object.prototype.toString.call(target[key]).slice(8, -1)
=== 'Object' || 'Array'){
result[key] = deepClone(target[key])
}else{
result[key] = target[key]
}
}
return result
}
5、判断数组类型有哪些方法?
(1)arr instanceof Array
(2)arr.constructor == Array
(3)Object.prototype.toString.call(arr) === '[object Array]'
(4)Array.isArray(arr)
6、== 和 ===
===为恒等符:当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时直接返回false。
==为等值符:当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。
a、如果一个是null、一个是undefined,那么相等。
b、如果一个是字符串,一个是数值,把字符串转换成数值再进行比较。
c、如果任一值是true,把它转换成1再比较;如果任一值是false,把它转换成0再比较。
d、如果一个是对象,另一个是数值或字符串,把对象转换成基础类型的值再比较。对象转换成基础类型,利用它的toString或者valueOf方法。js核心内置类,会尝试valueOf先于toString;例外的是Date,Date利用的是toString转换。那些不是JavaScript语言核心中的对象则通过各自的实现中定义的方法转换为原始值。
e、任何其他组合,都不相等。
7、undefined 和 null的区别
null 表示一个对象是“没有值”的值,也就是值为“空”;
undefined 表示一个变量声明了没有初始化(赋值);undefined不是一个有效的JSON,而null是;
undefined的类型(typeof)是undefined;
null的类型(typeof)是object;Javascript将未赋值的变量默认值设为undefined;
Javascript从来不会将变量设为null。它是用来让程序员表明某个用var声明的变量时没有值的。typeof undefined
//"undefined"
undefined :是一个表示"无"的原始值或者说表示"缺少值",就是此处应该有一个值,但是还没有定义。当尝试读取时会返回 undefined;
例如变量被声明了,但没有赋值时,就等于undefined
8、es6新特性
(1)let、const
(2) 模板字符串
(3)箭头函数
(4)函数参数默认值
(5)... Spread操作符
(6)对象和数组的结构赋值
(7)for...of 和 for...in
(8)ES6中的类,class语法
9、一句话概述什么是 promise
代表了未来某个将要发生的事件(通常是一个异步操作)
10、new操作符具体干了什么呢?
1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this 。var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
11、普通函数和箭头函数对比
12、JavaScript如何确定this
- 在调用函数时使用
new
关键字,函数内的this
是一个全新的对象。- 如果
apply
、call
或bind
方法用于调用、创建一个函数,函数内的 this 就是作为参数传入这些方法的对象。- 当函数作为对象里的方法被调用时,函数内的
this
是调用该函数的对象。比如当obj.method()
被调用时,函数内的 this 将绑定到obj
对象。- 如果调用函数不符合上述规则,那么
this
的值指向全局对象(global object)。浏览器环境下this
的值指向window
对象,但是在严格模式下('use strict'
),this
的值为undefined
。- 如果符合上述多个规则,则较高的规则(1 号最高,4 号最低)将决定
this
的值。- 如果该函数是 ES2015 中的箭头函数,将忽略上面的所有规则,
this
被设置为它被创建时的上下文。
11、js模拟一个call函数
// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.mycall = function (context) {
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
context = context || window
context.fn = this
let arg = [...arguments].slice(1)
let result = context.fn(...arg)
delete context.fn
return result
}
-
Vue
1、什么是MVVM
MVVM是Model-View-ViewModel的缩写。MVVM是一种设计思想。Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表视图,它负责将数据模型转化成UI 展现出来,ViewModel 是一个同步View 和 Model的对象。
在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
M:model,数据模型,可以在这里修改和操作数据
V:view,视图,用户看到的页面,负责将数据模型中的数据显示在页面上
VM:view-model是连接view和model的桥梁,模型和视图没有直接联系,而是通过vm进行一个双向交互
2、Vue的生命周期
总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。
- 创建前/后: 在beforeCreate阶段,vue实例的挂载元素el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,el还没有。
- 载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
- 更新前/后:当data变化时,会触发beforeUpdate和updated方法。
- 销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在
3、v-if和v-show的区别
v-if 是‘真正’的条件渲染,在切换过程中,条件块下的事件监听器和子组件会被适当地销毁和重建;只有当v-if第一次为真时,条件块才开始被渲染;它的切换开销比较高;
v-show 的元素无论初始值是什么元素总会被渲染,并且是通过css的display进行切换;它的初始渲染开销比较高。
所以需要频繁的切换用v-show比较好,条件改变很少时用v-if
v-show指令是通过修改元素的display CSS属性让其显示或者隐藏
v-if指令是直接销毁和重建DOM达到让元素显示和隐藏的效果
4、数据绑定、MVVM实现原理
5、双向数据绑定的原理
双向数据绑定是建立在单项数据绑定(model ==> view)的基础上的
双向数据绑定的实现流程:
a. 在解析v-model指令时,给当前元素添加input监听
b. 当input的value发生改变的值赋值给当前表达式所对应的data属性
(当data属性改变时,调用set()方法,通知dep属性变化,dep通知多有相关的watcher,watcher调用Updater更新视图)
6、vuex的理解
我们大概可以理解为vuex是一个公共 状态库
, 你可以在所有的组件里面去使用,修改。
以下是vuex的状态管理机制图
7、computed、methods和watch
computed计算属性,可以在里面定义变量,变量值是 对data的属性数据(或已经定义的计算属性)进行处理后 的结果值,比如商品列表和总金额,列表的增加减少等都会对总金额产生影响,总金额就可以在计算属性中定义;
methods方法也可以对data数据进行处理并返回,可以和计算属性达到完全相同的结果。
不同的是计算属性是基于响应式依赖进行缓存的,只有当相关的data数据发生改变时,才会重新执行函数进行求值,在此之前多次访问它则会立刻返回之前的计算结果;相比之下,通过方法进行多次访问,每次都会执行函数。【每当触发重新渲染时,调用方法将总会再次执行函数】
watch用来对data中的数据进行监视,它相比于计算属性的好处就是可以在数据变化时执行异步或开销比较大的操作
8、组件间的通信方式有哪些
-
相关编程题
1、函数节流
一个函数执行一次后,只有大于设定的执行周期后才会执行第二次。
- 有个需要频繁触发函数,出于优化性能角度,在规定时间内,只让函数触发的第一次生效,后面不生效
/**
* 函数节流
* @param fn 要被节流的函数
* @param delay 规定的时间
*/
function throttle(fn,delay) {
var lastTime = 0
return function () {
var nowTime = Date.now()
if(nowTime - lastTime > delay){
fn.call(this)
lastTime = nowTime
}
}
}
2、函数防抖
一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效
function debounce(fn,delay) {
var timer = null
return function () {
clearInterval(timer)
timer = setTimeout(() => {
fn.apply(this)
},delay)
}
}
3、排序算法
//冒泡排序
function bubble(arr) {
for(var i = 0; i< arr.length-1;i++){
for(var j = 0;j < arr.length-1-i;j++){
if(arr[j] > arr[j+1]){
var temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
//选择排序
/*
1、首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
2、再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3、重复第二步,直到所有元素均排序完毕。
时间复杂度:O(n2) 空间复杂度:O(1)
*/
function selectionSort(arr) {
var len = arr.length;
var minIndex, temp;
for (var i = 0; i < len - 1; i++) {
minIndex = i;
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { // 寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
//快速排序
/*
1、从数列中挑出一个元素,称为 “基准”(pivot);
2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
时间复杂度:O(nlogn) 空间复杂度:O(logn)
*/
function quickSort (arr) {
if (arr.length <= 1) { return arr; }
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++){
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
}
//插入排序
/*
1、将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
2、从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。
(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
时间复杂度:O(n2) 空间复杂度:O(1)
* */
function insertionSort(arr) {
var len = arr.length;
var preIndex, current;
for (var i = 1; i < len; i++) {
preIndex = i - 1;
current = arr[i];
while(preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex+1] = arr[preIndex];
preIndex--;
}
arr[preIndex+1] = current;
}
return arr;
}
//归并排序
/*
1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2、设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4、重复步骤 3 直到某一指针达到序列尾;
5、将另一序列剩下的所有元素直接复制到合并序列尾。
时间复杂度:O(nlogn) 空间复杂度:O(n)
*/
function mergeSort(arr) { // 采用自上而下的递归方法
var len = arr.length;
if(len < 2) {
return arr;
}
var middle = Math.floor(len / 2),
left = arr.slice(0, middle),
right = arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right)
{
var result = [];
while (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length)
result.push(left.shift());
while (right.length)
result.push(right.shift());
return result;
}
4、数组去重
//方法1
function fun1(arr) {
var t = []
t[0] = arr[0]
for(let i = 0;i < arr.length;i++){
for(let k = 0;k < t.length;k++){
//当原数组中的值和新数组中的值相同的时候,就没有必要再继续比较了,跳出内循环
if(t[k] == arr[i])
break;
//拿原数组中的某个元素比较到新数组中的最后一个元素还没有重复
if(k == t.length - 1){
//将数据插入新数组
t.push(arr[i]);
}
}
}
return t
}
//方法2:
function fun2(arr) {
const temp = []
arr.forEach(e => {
if (temp.indexOf(e) == -1) {
temp.push(e)
}
})
return temp
}
//方法3:
function fun3(arr) {
var temp = arr.filter(function (element, index, self) {
return self.indexOf(element) === index;
})
return temp
}
//方法4:
function fun4(arr) {
return Array.from(new Set(arr))
}
5、格式化金钱,每千分位加逗号
//方法1
function format1(str) {
let s = ''
let count = 0
for (let i = str.length - 1; i >= 0; i--) {
s = str[i] + s
count++
if (count % 3 == 0 && i != 0) {
s = ',' + s
}
}
return s
}
//方法2
function format2(str) {
return str.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
}
var a = '1234234'
console.log(format1(a)); //1,234,234
console.log(format2(a)); //1,234,234
6、数组降维(扁平化数组)
//方法一:递归
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
}
else {
result.push(arr[i])
}
}
return result;
}