CSS部分
W3C标准:
World Wide Web Consortium,中文是万维网联盟,也是web标准,就是我们所说的代码规范 对于结构的要求:1:标签字母小写、2:标签需要闭合、3:标签不能随意嵌套
对于CSS和JS的要求 使用外链css和js,使结构、表现、行为分为三个部分符合规范,便于维护,同时提高页面渲染速度,提高用户体验
尽量减少行内样式,使结构与表现分离,id与class命名要见文知义,标签越少加载越快,方便改版,有利于用户体验
是一系列标准的集合,因网页由三个部分组成,分别是结构层(HTML)、表现层(CSS)、行为层(JS)
HTML层叠等级
盒模型:
作用:页面渲染时,dom元素所采用的布局模型。
可通过box-sizing进行设置。根据计算宽高的区域可分为
border-box(IE盒模型)----- width = border+padding+content
content-box(W3C标准盒模型)----- width = content
问:怎么样才算是选择了‘W3C标准’的盒模型呢? 在网页的顶部加上 doctype 声明。假如不加 doctype 声明,那么各个浏览器会根据自己的行为去理解网页,加上了 doctype 声明,那么所有浏览器都会采用标准 w3c 盒子模型去解释你的盒子,网页就能在各个浏览器中显示一致了
解决浏览器兼容的问题 blog.csdn.net/weixin_3853…
实现居中的几种方式:
水平居中:
行内元素:text-align:center
块元素:
margin:0 auto
父元素:position:relative,子元素:position:absolute ,left:50%, transform:transaleX(-50%)
父元素:display:flex , justify-content:center
垂直居中:
行内元素:line-height:父元素的高度
块元素:
父元素:display:table,子元素:display:table-cell,vertical-align:middle
父元素:position:relative,子元素:position:absolute,top:50%,transform: translateY(-50%)
父元素:display:flex,align-items:center
水平垂直居中
display:flex,justify-content:center,align-items:center
父元素:position:relative,子元素:position:absolute,top:50%,left:50%,transform:translate(-50%,-50%)
position:absolute, left:0;top:0;right:0;bottom:0;margin:auto
CSS优先级:
!important > 行内 > head内部的style标签样式 > 外链CSS
选择器优先级 #id > .class > tag标签 > * > 默认
link 与 @import 的区别
link兼容性比@import好,@import需要IE5以上才能兼容
link可以用JS动态引用,@import不行
link的功能要比@import多,可以定义 RSS,定义 Rel 等作用,@import只能用于加载 css
CSS预处理器(Sass/Less/PostCss)
原理:将类CSS通过webpack编译成浏览器可读的真正的CSS格式。在这层编译之上,css有了更强大的功能:
常用功能:
嵌套
变量
循环语句
条件语句
自动前缀
单位转换
mixin复用
CSS动画
animation
transition
BFC---Block Formatting Context
blog.csdn.net/sinat_36422…
BFC是一个独立的布局环境,其中的元素布局是不受外界的影响,并且在一个BFC中,块盒与行盒(行盒由一行中所有的内联元素所组成)都会垂直的沿着其父元素的边框排列
formatting context页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用
布局的规则:
内部的box会垂直方向一个接一个的放置
Box垂直方向的距离是由margin决定。同一个BFC内的两个相邻的box的margin会重叠
每个盒子的margin box的左边会和border box的左边相接触,即使是浮动也是如此
BFC的区域不会与float box重叠
BFC是一个独立的布局空间,内部的子元素不会受外部影响
BFC计算高度会包含浮动的部分
触发的方法:
position不是static和relative
overflow不是visible
float不是none
display:inline-block / table
使用场景:
避免margin重叠
自适应两栏布局
包含浮动元素,清除内部的浮动
Flex布局:阮一峰老师的Flex布局教程:语法篇
JS部分
栈内存与堆内存
栈内存:
存储基本数据类型的变量,如:Number,Boolean,String,Null,Undefined
大小已知,或范围有上限
值传递
速度快
堆内存:
存储Object
大小未知,几乎无限制
引用传递
速度没有栈内存快
问:为什么基本数据类型保存在栈内存中,而饮用数据类型保存在堆内存中
答:因为基本数据类型稳定,且相对内存占用小,并且栈内存的查找速度要比堆内存快,两者存放优选栈内存,但因为引用数据类型的大小不固定,且堆内存的大小无限制,故此堆内存中
1、原型、构造函数、实例
原型(prototype):
一个简单的对象,用于实现对象的属性继承。可以简单的理解为对象的父亲
每一个JS对象都拥有一个_proto_的属性,可以通过obj._proto_来获取该对象的原型
原型对象的好处是可以让所有对象实例共享它所包含的属性和方法
构造函数:
一个可以通过new来新建对象的函数
实例:可以通过构造函数和New新建的对象叫做实例。实例通过_ptoyo_指向原型,原型通过constructer指向构造函数
三者的关系:
构造函数.prototype ----> 原型
实例.-proto- ----> 原型
原型.constructor ----> 构造函数
构造函数.proto == Function.prototype
prototype和_proto_的区别:
prototype是每个函数都有的一个属性,对象没有,通过bind绑定的没有这个属性,通过这个属性来指向原型
_proto_是每一个JS对象有的(函数也是对象(null除外)),通过obj._proto_来指向该对象原型
2、原型链
原型链是由原型对象组成,每个对象都有一个_proto_属性,指向创建该对象的构造函数的原型,_proto_将对象链接起来组成了原型链。是一个用来实现继承和属性共享的对象链
属性查找机制:
当查找对象的属性时,会先从实例对象上去找,如果实例对象上不存在这个属性会沿着原型链向上查找,找到输出,找不到继续找,直到找到Obj.prototype上,如果还是没有,则输出undefined
属性修改机制:
当要修改对象的属性时,如果实例对象上存在这个属性则直接修改,如果不存在则会创建这个属性。
如果要修改原型的属性时,可以通过obj.prototype.XXX来修改,但是会造成所有继承于这个对象的实例的属性值发生改变
检验属性时存在于实例上还是存在于原型上:hasOwnProperty 属性只有存在于实例中才会返回true 例:obj.hasOwnProperty('name') Obj.prototype没有原型,obj.prototype.proto 为null
3、在new过程中都经历了哪些
创建一个新的对象
将这个新对象的_proto_属性指向构造函数的原型
构造函数的内部this指向这个新对象
将这个新对象返回(如果构造函数有返回值,则会返回该值)
4、执行上下文(EC)
包含的部分
变量对象
作用域
this
它的类型
全局执行上下文
函数执行上下文
eval执行上下文
代码执行过程
创建全局执行上下文
全剧执行上下文,开始自上而下的执行,遇到函数时,函数执行上下文被推到执行栈顶层
函数执行上下文被启动后,开始执行函数中的代码,函数执行上下文被挂起
函数执行完成后,会被移除执行栈,控制权交还给全局执行上下文,继续执行
5、变量对象:
每一个执行环境中都会有一个变量对象,用于存储当前执行环境的所有变量和函数,不包含函数表达式
执行上下文的一部分,可以抽象理解为数据作用域。存储着执行上下文中的所有的变量和函数声明(不包含函数表达式)
函数声明:var fn = function(){}
函数表达式:function fn(){}
6、作用域
一个变量或者函数的可访问的范围,控制着变量和函数的可见性与周期,包括全局作用域、函数作用域和ES6新增的块级作用域。块级作用域就是{}之间的作用域
通过var 在函数外部定义的变量是全局作用域,在函数内部和函数传过来的参数的作用域只在函数内部,window对象的所有属性拥有全局作用域
7、作用域链:
在执行上下文中可以访问到父级变量甚至全局变量,这就是作用域链的功劳。它是一组对象列表,包含父级和自身的变量对象,因此我们能通过作用域链访问到父级里声明的变量和函数
8、闭包:
有权限访问上一层父级作用域的变量的函数。当父函数被销毁时,被return 回来的子函数[[scope]]中仍保留着父函数的变量和作用域,因此仍然可以访问父级的变量(立即执行函数其实就是个闭包)
优点:
拥有独立的作用域,避免变量污染
缺点:
外部函数无法访问这个独立作用域内的变量和函数
内部所有函数都指向一个父级函数,父级变量被修改时会影响所有子级函数
解决方案:
通过传参数的形式来获取内部变量、window对象上挂 ----- 对应缺点 1
通过setTimeout进行包裹,通过第三个参数传入 ----- 对应缺点 1
使用块级作用域,避免变量共享 ----- 对应缺点 2
9、script的引入方式
HTML静态引入
JS 动态引入
var script = document.createElment('script')
script.type = 'text/script'
script.url="app.js"
延迟加载
<script src="app.js" defer></script>
按顺序加载,但是页面渲染完成后,DOMContentLoaded之前执行,
异步加载
<script src="app.js" async></script>
页面渲染的过程中同时加载js文件 延迟加载和异步加载都是异步的
10、对象的拷贝(深拷贝和浅拷贝)---->只针对引用数据类型
浅拷贝:
通过赋值的形式拷贝引用对象,但仍指向堆内存中的同一个地址,新对象修改时原对象也会受影响
1、 使用Object.assgin({},obj) 实现浅拷贝
let newObj1 = {
name:'LiSi',
age:'22',
innerObj:{
sex:'gril'
}
}
let newObj2 = Object.assign({},newObj1)
2、展开运算符(...obj)
let _obj1 = { name:{name:'张三'} ,age:21}
let _obj2 = {sex:'girl',class:5}
let _obj = {..._obj1, ..._obj2}
深拷贝:创造一个一摸一样的新对象,新对象和原对象不共享内存,新对象修改时原对象不会受影响
1、递归实现深拷贝 方法一:
function isObject(obj){
return Object.prototype.toString.call(obj) === '[object Object]' ||
Object.prototype.toString.call(obj) === '[object Array]'
}
function deepCopy(source, hash = new WeakMap()){
if(!isObject(source)) return source
if(hash.has(source)) return hash.get(source)
let res = Array.isArray(source) ? [] : {}
hash.set(source, res)
for(let key in source){
if(Object.prototype.hasOwnProperty.call(source, key)){
if(isObject(source[key])){
res[key] = deepCopy(source[key], hash)
}else{
res[key] = source[key]
}
}
}
return res
}
方法二: //实现深拷贝函数
function deepClone(data) {
const type = this.judgeType(data);
let obj = null;
if (type == 'array') {
obj = [];
for (let i = 0; i < data.length; i++) {
obj.push(this.deepClone(data[i]));
}
} else if (type == 'object') {
obj = {}
for (let key in data) {
if (data.hasOwnProperty(key)) {
obj[key] = this.deepClone(data[key]);
}
}
} else {
return data;
}
return obj;
}
function judgeType(obj) {
// tostring会返回对应不同的标签的构造函数
const toString = Object.prototype.toString;
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object',
};
if (obj instanceof Element) {
return 'element';
}
return map[toString.call(obj)];
}
2、JSON.stringify+JSON.parse 实现深拷贝
function deepCopy(obj){
let _obj = JSON.stringify(obj)
let _newObj = JSON.parse(_obj)
return _newObj
}
JSON.stringify+JSON.parse的弊端
- 如果被拷贝的对象里有时间对象,被拷贝的对象的时间将只是字符串的形式,不再是时间对象
- 如果被拷贝的对象中有RegExp、Error对象,将会只得到空对象
- 如果被拷贝的对象中有函数、undefined,序列化的结果会把函数和undefined丢失
- 如果被拷贝的对象中有NaN、Infinity和-InfiniTy,序列化的结果会变成null
- 如果被拷贝的对象是由构造函数生成的,序列化后会丢失constructor(JSON.Stringify只能序列化对象的可枚举的自有属性)
- 如果被拷贝的对象有循环引用的情况下,无法实现正确的深拷贝
11、instanceof原理
在实例的原型链中是否能找到该构造函数的protoType所指向的原型,是为true
// L intanceof R
function intense_of(L,R){
let O = R.prototype // 构造函数拥有protoType属性
let L = L._proto_ // 实例对象拥有_proto_属性
while(true){
if(L === null){
return false
}
if(O === L){
return true
}
let L = L._proto_
}
}
12、代码的复用
当代码开始写第二遍时,就要考虑到代码的复用
- 方法:
- 函数封装
- 继承
- 复制 extend
- 借用 call/apply
- call和apply是Function.prototype的方法,作用是用来改变this指向
- call传参需要逐一列出,而apply接受一个数组
- 混入 mixin
13、继承(9种模式) ----> 一般是指原型链继承,通过原型链继承指定原型的属性和方法。
继承的方法:
原型链继承----->将父类的实例指向子类的原型
son.protoType = new Parent()
构造函数继承 -----> 复制父类的实例属性给子类
Parent.call(this)
组合继承(原型链继承+构造函数继承)
function Son(){
Parent.call(this)
}
Son.prototype = new Animal()
缺点:调用了两次父级构造函数,生成了两个实例,子类实例屏蔽了子类原型上的实例
实例继承 ----> 给父类实例添加属性,作为子类返回
function Son(args){
var instance = new Parent(args)
instance.sleep = function(){}
return instance
}
var son = new Son({ name:'Tom' })
原型式继承---->借助原型基于已有的对象创建新的对象
方法一:let Son = Object.create(Parent)
方法二:
function object( O ){ // 传递一个字面量函数
function F(){} // 创建一个构造函数
F.prototype = O // 将字面量函数赋值给构造函数的原型
return new F() // 返回构造函数的实例
}
寄生式继承(基于原型式继承+工厂模式) -- 创建一个用于封装继承过程的函数,在函数内部增强对象, 最后返回这个对象
function object(o){
function F(){}
F.prototype = o
return new F()
}
function createAnother(original){
var clone = object(original) // 调用函数创建一个新的对象
clone.fn = function(){} // 增强这个对象
return clone // 返回这个对象
}
寄生组合式继承--> 借用构造函数+建立子类与父类的链接
function SuperType(name){
this.name = name
this.colors = ['red','green','yellow']
}
SuperType.prototype.sleep = function(){ alert('prototype') }
function SubType(name,age){
SuperType.call(this,name)
this.age = age
}
function inherit(SubType,SuperType){
var proto = SuperType.prototype
proto.constructor = SubType
SubType.prototype = proto
}
inherit(SubType,SuperType)
圣杯模式
let inherit = (function(){
function F(){} //函数是一个闭包,仅用做一个缓冲而不做他用
return function(son,parent){
F.prototype = parent.prototype
// son.prototype = F.prototype; /* 不能这么写,因为这样写就相当于对象son、parent和 F 共享原型了 */
son.prototype = new F() // 使对象targetSon试图修改自身属性时仅仅是以buffer函数作为对象进行修改,而不会影响到其他对象
son.prototype.uber = parent.prototype
son.prototype.constructor = son
}
})()
es6语法糖 class / extends
注:一定要有super,否则新建实例时会报错。因为子类没有自己的this对象,需要继承父类的this对象,如果没有super子类就得不到父类的this对象
class Parent{
constructor(props){
this.name = props.name || 'Unknown'
}
eat(){
console.log(this.name + '会来吃东西')
}
}
class Son extends Parent{
constructor(props,myAttribute){
super(props)
this.type = props.type
this.attr = myAtrribute
}
play(){ console.log('子类的私有方法') }
myattr(){ console.log('子类的自由方法二') }
}
ES5的构造函数与es6的class的区别:
- es6的class不存在变量提升
- es6中的class使用严格模式,引入一个未定义的变量会报错
- es6中class所有的方法是不可枚举的
- es6中class的调用必须使用new
- class无法重写类名
- class有两条继承链:
- 子类的—proto—指向父类
- 子类prototype属性的—proto—指向父类的prototype
- es6子类通过-proto-找到父类,es5字类通过-proto-找到Function.prototype
- class与构造函数生成this的顺序不同
- 构造函数先建立子类实例对象的this,再调用父类构造函数修饰子类实例
- es6中class是先建立父类实例对象的this,在调用子类的构造函数修饰this。所以在子类的constructor中必须要有super,这样才能正确的使用this
- 因为生成this的顺序不同,所有es5不能直接继承原生构造函数,而es6可以
14、类型转换
- -、*、/、%:全部转化成数值后计算
- 数字 + 字符串 = 字符串
- 数字 + 对象,优先调用对象valueOf -> toString
- 数字 + boolean/null = 数字
- 数字 + undefined = NaN
- [1].toString === '1'
- {}.toString() === '[Object Object]'
- NaN !== NaN、+undefined为NaN
15、类型判断
- 判断目标类型,typeOf并不能完全满足。这不是bug只是JS中万物皆对象的理论,需要我们区别对待
- 基本类型-->null:String( null )
- 基本类型-->String\Number\Boolean\undefined\function 可以用typeOf来判断
- 引用类型-->Array\Date\Regxp Error 调用toString后根据[oject XXX]来判断
- 引用类型通过Object.prototype.toString.call(被判断的对象),不使用toString是因为toString被数组重写过,会直接将数组转成字符串,失去类型判断的功能
判断数组类型的多种方法:
- array instanceOf Array
- array.proto == Array.prototype
- arr.constructor == Array
- Array.isArray(arr)
- Object.prototype.toString.call(arr) === '[Object Array]'
16、模块化
模块化现在是前端开发很重要的思想,它提高了项目的可维护、可拓展、可协作性。通常在浏览器中使用ES6的模块化支持,在node使用commmonJS的模块化支持
- ES6: import / export
- commonJS:require / module.exports / exports(同步)
- AMD:require / defind(异步)
- CMD:seaJs
- UMD:兼容CommonJS和AMD,目前多数模块的封装,既可以在 Node.js 环境又可以在 Web 环境运行,所以一般会采用 UMD 的规范 require与import的区别
- require支持动态导入,import不支持,正在提案 (babel 下可支持)
- require是 同步 导入,import属于 异步 导入
- require是 值拷贝,导出值变化不会影响导入值;import指向 内存地址,导入值会随导出值而变化
17、函数执行改变this
this的概念:由于JS的设计原理,在函数中,可以引用运行环境中的变量,因此就需要一个机制让我们能在函数内部获取当前的运行环境,这便是this。要想弄明白this,就需要知道函数的运行环境,通俗一点就是谁调用了函数
例如:obj.fn() 那么运行环境就是obj,this指向的是obj
fn()可以理解为window.fn() 那么运行环境就是window,this指向的是window
因为这个机制不能完全满足我们的需求,因此提供了三种手动修改this指向的方法
call:fn.call(this,arg1,arg2,arg3)
apply:fn.apply(this,[arg1,arg2,arg3])
bind :fn.bind(this)(1,2)
call、apply是会直接运行的,而bind不会运行,而是但会一个新的方法
18、事件机制
事件委托:www.cnblogs.com/liugang-vip…
事件捕获:document --> html --> body --> div
事件冒泡:div --> body --> html --> document(IE浏览器只支持冒泡事件,attachEvent没有第三个参数)
事件绑定的类型:
div.onclick = function(){} 。
删除事件div.onclick = null / div.onclick = false
IE9+:target.addEventListener(type, fn, true<捕获>/false<冒泡>)、 target.removeEventListener()来绑定和移除事件
IE8之前:div.attachEvent()、div.detachEvent()
三种类型的区别:
- addEventListener可以为一个事件添加多个相应的函数,彼此之间不会覆盖,而onclick则不可以
- onclick支持所有的浏览器,而addEventListener不支持IE浏览器,IE浏览器的事件添加需要用attachEvent,由于IE浏览器只支持冒泡,所以attachEvent没有第三个参数
- onclick在注销时,只需要将属性值设置为null即可,用addEventListener注册的事件注销时需用removeEventListener,且参数要与addEventListener是一样的,所以addEventListner指定的函数不能是一个匿名函数,否则注销不了。虽然函数内容一样,但是函数对象是不一样的
- attachEvent回调函数中的this是window,而onclick回调函数中的this是当前捕获事件的对象
- 浏览器事件兼容性处理
document加载事件回调
DOMContentLoaded:在dom树加载完成之后被触发,不等待css、js、img等下载< 兼容性较差 >
onload:等待页面的内容都加载完毕之后被触发
函数柯里化: 在一个函数中,填充几个参数后返回一个新的函数的技术。
babel的编译原理:
- Babylon 先将ES6、ES7的语法转成AST
- babel-traverse 将AST进行编译转换,生成新的AST
- babel-generator将新的AST转成ES5语法
19、数组(Array)
- foreach
- map
- every
- some
- filter
- find
- reduce
- push后增 / pop后删 返回添加或删除的元素,改变原数组
- unshift 前增 / shift 前删
- sort 排序 / reverse 倒叙
- slice(start, end) 截取 -1代表最后一项,-2最后一项的前一项
- splice(index,howmany,item1,…… ,itemN) 删除、新增、替换
- concat
- indexOf 20、创建对象的方式
var obj = new Object()
var obj = {}
function F(){} var obj = new F()
Object.create()
21、实现AJAX的基本步骤 www.cnblogs.com/Ann-web-1/p…
- 创建xmlHttpRequest对象,也就是创建一个异步调用对象
- 创建一个新的http请求,并指定该http请求的方法,URL以及验证信息
- 设置相应http请求状态变化的函数
- 发送http请求
- 获取异步调用返回的数据
- 使用JS和DOM完成局部刷新
23、JS防抖与节流(性能优化)
防抖:将高频率操作优化成只在最后一次执行。比如用户输入内容完成之后做一次校验即可
节流:将高频率操作转化成低频率操作。比如鼠标的滚动事件、滑动事件,可以将它们处理成每隔一段时间后执行一次
24、JS的DOM和BOM
JS的三大部分:BOM、DOM、ECMAScript
BOM:浏览器对象模型
- 最顶层的对象是window
- BOM提供了很多方法来操作或者访问浏览器
- 比如获取浏览器的宽度高度、新增关闭窗口、获取URL、跳转页面、setTimeout、弹窗(alert、confirm)
DOM:文档对象模型 - 最顶层的元素是document
- 存储在页面树形结构中
阮一峰老师的generator相关章节
//Promise的then(resolve, reject)和catch()
//Promise状态从pending到resolve之后肯定会走then的第一个参数,resolve函数
//Promise状态从pending到reject之后,如果then有第二个参数reject,则执行reject,如果没有第二个参数reject则执行cathch。reject的优先级高于catch
//如果是网络异常(比如断网),会直接进入catch而不会进入then的第二个回调reject
class Promsie {
constructor(fn) {
//三个状态
this.status = 'pending',
this.resolve = undefined;
this.reject = undefined;
let resolve = value => {
if (this.status === 'pending') {
this.status = 'resolved';
this.resolve = value;
}
};
let reject = value => {
if (this.status === 'pending') {
this.status = 'rejected';
this.reject = value;
}
}
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onResolved, onRejected) {
switch (this.status) {
case 'resolved': onResolved(this.resolve); break;
case 'rejected': onRejected(this.resolve); break;
default:
}
}
}
Promise.race = function(promises) {
if (!Array.isArray(promises)) {
throw new Error("promises must be an array")
}
return new Promise(function (resolve, reject) {
promises.forEach(p =>
Promise.resolve(p).then(data => {
resolve(data)
}, err => {
reject(err)
})
)
})
}
25、什么异步?
- 异步编程的4种方法
- 回调函数
- 事件监听
- 发布、订阅
- promise
promise实现: blog.csdn.net/qq_40619263…
Generator 可以终止执行流程
async和await
async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。是generator的语法糖
JS设计中支持回调,回调产生了闭包,闭包就是把自己的变量暴露给其他的对象,但是其他对象什么时候执行他并不知道,因此产生了异步。
因此异步其实就是在使用回调时所产生的一个结果
为什么异步会被大量的使用:
首先执行时间长不代表资源消耗就多。比如ajax,它会发送一个请求,然后等待请求的响应。请求的过程中时间消耗比较大,但是对于JS的执行环境而言,并没有任何的资源消耗,所以其实这时候的主线程是闲置的状态。如果不用异步的话,主线程所做的事情就是等待请求的响应。那么,其实是可以把等待这个过程的线程利用起来的,让他去做别的事情,等请求响应以后再来做这个事情。这就形成了异步
异步看似和多线程相似,都是在前面程序没有执行完的时候就做了其他的事情,但是其实是有本质区别的。如果不是这种等待的操作,而是运算比较大的那种,就会造成线程的阻塞。这个时候就需要利用多线程来解决这个问题
回过来说,JS是怎么利用这个等待资源的呢,这和JS的运行机制有关系,JS的运行机制会把所有需要执行的放在一个执行队列中去,像这样需要等待的程序,它会在执行了以后开始等待的时候把它放到执行队列的后面去,直到等待的过程结束后,再来执行它的回调
26、回流和重绘
juejin.cn/post/684490…
27、for in和for of的区别
- for in 遍历数组
- 1、遍历的返回值是index,且是字符串的形式,不能直接进行几何运算
- 2、顺序有可能不是array的实际内部顺序
- 3、会遍历出数组所有的可枚举属性,包括原型
- 4、for in更加适合遍历对象
for of
- 可以遍历有迭代器对象的对象,比如数组、字符串等。不能遍历普通对象,因为普通对象没有迭代器对象,如果要遍历对象的话可以内置Object.keys(obj)
- for……in 遍历对象返回key值,遍历数组返回下标index
- for……of 不能遍历对象,如果遍历需要使用 Object.keys(obj),返回key,遍历数组返回对应的项
28、事件循环 juejin.cn/post/684490…
浏览器(多进程)包含了Browser进程(浏览器的主进程)、第三方插件进程和GPU进程(浏览器渲染进程)
其中GPU进程(多线程)和Web前端密切相关,包含以下线程: - GUI渲染线程
- JS引擎线程
- 事件触发线程(和EventLoop密切相关)
- 定时触发器线程
- 异步HTTP请求线程 GUI渲染线程和JS引擎线程是互斥的,为了防止DOM渲染的不一致性,其中一个线程执行时另一个线程会被挂起。
JS引擎线程和事件触发线程
浏览器页面初次渲染完毕后,JS引擎线程结合事件触发线程的工作流程如下:
(1)同步任务在JS引擎线程(主线程)上执行,形成执行栈(Execution Context Stack)。
(2)主线程之外,事件触发线程管理着一个任务队列(Task Queue)。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
(3)执行栈中的同步任务执行完毕,系统就会读取任务队列,如果有异步任务需要执行,将其加到主线程的执行栈并执行相应的异步任务。
事件循环机制(Event Loop)
事件触发线程管理的任务队列是如何产生的呢?事实上这些任务就是从JS引擎线程本身产生的,主线程在运行时会产生执行栈,栈中的代码调用某些异步API时会在任务队列中添加事件,栈中的代码执行完毕后,就会读取任务队列中的事件,去执行事件对应的回调函数,如此循环往复,形成事件循环机制
任务类型
JS中有两种任务类型:
微任务(microtask)和宏任务(macrotask),在ES6中,microtask称为 jobs,macrotask称为 task。
宏任务: script (主代码块)、setTimeout 、setInterval 、setImmediate 、I/O 、UI rendering
微任务:process.nextTick(Nodejs) 、promise 、Object.observe 、MutationObserver
这里要重点说明一下,宏任务并非全是异步任务,主代码块就是属于宏任务的一种(Promises/A+规范)。
它们之间区别如下:
- 宏任务是每次执行栈执行的代码(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
- 浏览器为了能够使得JS引擎线程与GUI渲染线程有序切换,会在当前宏任务结束之后,下一个宏任务执行开始之前,对页面进行重新渲染(宏任务 > 渲染 > 宏任务 > ...)
- 微任务是在当前宏任务执行结束之后立即执行的任务(在当前 宏任务执行之后,UI渲染之前执行的任务)。微任务的响应速度相比setTimeout(下一个宏任务)会更快,因为无需等待UI渲染。
- 当前宏任务执行后,会将在它执行期间产生的所有微任务都执行一遍。
根据事件循环机制,重新梳理一下流程:
- 执行一个宏任务(首次执行的主代码块或者任务队列中的回调函数)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有任务(依次执行)
- GUI线程渲染完毕后挂起,JS引擎线程执行任务队列中的下一个宏任务
箭头函数与普通函数的区别
blog.csdn.net/z591102/art…
- 箭头函数没有prototype,所以就没有this指向
- 箭头函数没有自己的this,箭头函数在定义时继承自外层第一个普通函数的this。所以箭头函数的this,在它被定义时就已经确定了,不会被改变
- call | apply | bind不能改变箭头函数的this指向
- 箭头函数不能作为构造函数使用
- 箭头函数不绑定arguments,用( ...rest)来代替arguments访问函数的参数列表
- 箭头函数不能用作generator,不能使用yeild关键字
VUE
v-show 与 v-if的区别
v-if是通过创建和销毁DOM来实现的。可以说是真正的条件渲染,因为他会确保条件块内的事件监听和子组件适当的销毁和创建。如果初始条件为假则不创建,直到条件变为真时才开始渲染代码块
v-show是通过CSS的display来实现的,不管初始条件是真是假,都会创建代码块,然后通过改变display的属性进行切换
注:v-if适合使用在很少改变条件,不频繁切换的场景中,而v-show则适用于非常频繁切换条件的场景
v-if 和v-for
css和style是怎么绑定的 CSS绑定: 对象绑定-->
<div :class="{isShow:isShowFlag,'text-style':isHasText}"></div>
数组绑定-->
<div :class="[isActive ? activeClass : '', errorClass]"></div>
Style绑定: 对象绑定-->
<div :style="{color:activeColor , fontSize: fontsize + 'px'}"></div>
数组绑定-->
<div :style="[styleColor,styleSize]"></div>
怎样理解VUE的单项数据流
所有的prop使其父子prop之间形成了一个单向下行绑定。父级prop的更新会向下流动到子组件中。但是反过来则不行。这样可以防止子组件的意外操作改变了父组件的状态,使我们的应用的数据流向变得难以理解。
父级组件每次更新时,子组件的prop会被自动更新为最新的值,这意味着不应该在子组件内部修改prop。如果这样做了就会报错。子组件要想修改只能通过$emit派发一个自定义事件,父组件接到后,由父组件完成修改
computed和watch的区别及使用场景
segmentfault.com/a/119000001…
- computed:计算属性,依赖于其他的值,并且computed的值有缓存,只有它依赖的属性值发生改变时,下一次获取的computed值的时候才会重新计算computed的值
- watch:更多的是「观察」的作用,类似某些数据的监听回调,每当监听的数据发生改变时都会执行回调来进行后续操作 场景的区别:
- computed:当我们需要数值计算时,且依赖于其他数据时,可以用computed。可以利用它的缓存特性,来避免每次取值时,都要重新计算
- watch:当我们需要在数据变化时进行一些异步操作时,应该使用watch。
直接给一个数组项赋值,VUE能检测到变化嘛
vue不能检测到以下两种情况的数组变动
当利用索引直接设置一个数组项时
vm.items[indexOfItem] = newValue
当改变数组长度时
vm.items.length = newLength
为解决问题一,vue提供了以下解决方案:
Vue.set( vm.items, indexOfItem, newValue )
vm.$set( vm.items, indexOfItem, newValue)
vm.items.splice( indexOfItem, 1, newValue )
为解决问题二,vue提供以下操作方法
vm.items.splice( newLength )
生命周期
生命周期的定义
VUE实例有一个完整的生命周期。从开始创建实例,初始化数据,编译模板,挂载DOM-->渲染、更新DOM-->渲染,销毁等一系列的过程,称之为生命周期
各个生命周期的作用
- beforeCreate 组件创建之初,此时组件的属性和事件都是默认的,其他的东西还没有创建,data和methods未被初始化
- created 组件创建完成,此时data和methods已经初始化完成,可以调用methods或操作data
- beforeMount 模板已经被编译完成,但只存在在内存中,还未挂载至页面上
- mounted 模板已经被挂载到页面上,此时整个VUE实例初始化已完成
- beforeUpdate data中的数据已经被更新,但是页面上的数据还是旧数据
- updated 组件中data数据已经和页面同步
- activated keep-alive专属,组件被激活时调用
- deactivated keep-alive专属,组件被销毁时调用
- beforeDestroy 组件销毁前调用
- destroyed 组件销毁后调用
c. 生命周期示意图
vue的父组件和子组件生命周期函数的执行顺序
父子组件生命周期钩子函数执行顺序可以分为以下四类:
- 创建,加载渲染过程
父组件befroeCreate --> 父组件created --> 父组件beforeMount --> 子组件beforeCreate --> 子组件created --> 子组件beforeMount --> 子组件mounted --> 父组件mounted - 子组件更新 父组件beforeUpdate --> 子组件beforeUpdate --> 子组件updated --> 父组件updated
- 父组件更新 父组件beforeUpdate --> 父组件updated
- 销毁 父组件beforeDestroy --> 子组件beforeDestroy --> 子组件destroyed --> 父组件destroyeda
可以在哪个生命周期中发送异步请求
可以在create、before Mount、mounted中进行异步请求,因为在这三个函数中,data已经初始化完成,可以用来处理服务端返回的数据。但是最好还是在created中发送异步请求,因为:
1、能更快的获取到服务端返回的数据,减少页面的loading时间
2、SSR不支持beforeMount、mounted
父组件可以监听到子组件的生命周期吗?
可以。如果父组件监听到子组件挂载mounted后需要做一些处理,可以通过以下两种方式完成:
通过子组件暴露$emit
// Parent
<Child @mounted='doSomething' />
// child
mounted(){
this.$emit('mounted')
}
通过@hook
// Parent.vue
<Child @hook:mounted="doSomething" ></Child>
doSomething() { console.log('父组件监听到 mounted 钩子函数 ...'); }
// Child.vue
mounted(){ console.log('子组件触发 mounted 钩子函数 ...'); },
谈谈你对keep-alive的理解
keep-alive是vue内置的组件,可以使被包含的组件保留状态,避免重新渲染:
结合路由动态,用于缓存组件
提供了include和exclude两个属性,两者都支持字符串和正则表达式
- include:被匹配的组件会被缓存,不需要重新渲染
- exclude:被匹配的组件不会被缓存,需要重新渲染,exclude优先级大于include 对应两个钩子函数
- activated:组件被激活时触发
- deactivated:组件被销毁时触发
组件中的data为什么是一个函数
因为组件是用来被复用的,且JS中对象是引用关系。如果data是一个对象,那么每个组件之间的作用域就没有被隔离,子组件中的data属性值会被相互影响。但当它是一个函数的时候,每个组件都有一个可独立维护的作用域,可以避免组件之间data属性值的相互影响
v-model的原理
在项目中我们主要是用v-model指令在表单input、textarea、select、checkbox、radio上创建双向绑定。
v-model其实只是一个语法糖,v-model在内部为不用的元素使用不同的属性抛出不同的事件
- textare、text、input使用value属性+input事件
- checkbox和radio使用checked属性+change事件
- select使用将value作为prop,并使用change事件
<input v-model='somethings'>
相当于
<input v-bind:value='somethings' v-on:input='somethings = $event.target.value'>
vue-router路由模式有几种
hash
使用URL Hash值来做路由,支持所有浏览器。包括不支持HTML5 History API的浏览器
history
依赖HTML History API和服务器配置,history.
abstract
支持所有的javascript运行环境,如nodejs服务器。如果发现没有浏览器API,路由将会强制进入这个模式
v-router中常用的hash和history的实现原理
hash的实现原理:
早期前端的路由主要是通过location.hash来实现的。原理也很简单,location.hash的值就是URL中‘#’后面的内容
hash实现原理主要基于以下几个特性:
- hash是客户端的一个状态,因此在向服务器端发送请求时,hash不会被发送
- hash值的改变,会在浏览器访问记录中新增一条记录,因为可以通过浏览器的前进、后退按钮来完成hash的切换
- 可以通过a标签并设置href属性,当用户点击这个标签时会改变hash的值。或者可以通过JavaScript对location.hash进行赋值,来完成hash值的改变
- 可以通过hashchange来监听hash值的改变,从而完成页面的切换
history的实现原理:
依赖于HTML5中的History API来实现URL的改变。其中最主要的两个API是history.pushState 和history.replaceState。这两个API可以在不进行刷新的情况下,操作浏览器的历史记录。不同的是前者是新增一条历史记录,后者是直接替换成当前的历史记录
history实现原理主要基于以下几个特性: - pushState和replaceState两个API来实现URL的改变
- 可以通过popstate事件来监听URL的改变,从而进行切换
- history.pushState 和history.replaceState不能触发popstate,因此需要我们手动触发页面跳转
vue是如何实现数据双向绑定的?
采用数据劫持结合发布-订阅设计模式,通过object.defineProperty劫持各个属性的getter和setter,在属性值发生变化时发送消息给订阅者,触发相应的监听回调
具体如下:
- 实现一个监听器Observer,用来劫持和监听各个属性,属性值一旦发生变化,就会发消息给订阅者
- 实现一个解析器Compile,用来解析VUE的模板指令,将模板中的变量转化成数据,初始化页面视图。并将指令对应的节点绑定更新函数,添加用于监听数据的订阅者,一旦数据发生变化,收到通知后,触发更新函数更新数据
- 实现一个订阅者watcher,它是Observer和Compile的桥梁,主要是订阅Observer属性值变化的消息,当收到属性值变化的消息后,会触发compile的更新函数
- 实现一个订阅器Dep,它是发布-订阅的设计模式,用来收集订阅者watcher,将watcher和observer进行统一管理
Vue框架怎么实现对象和数组的监听?
www.jb51.net/article/107…
Vue将data中的数组的一些方法进行了重写,指向了自己定义的数组原型方法。这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化
vue包含了一组观察数组变化的方法push、pop、unshift、shift、sort、reverse、splice方法进行了重写。在使用这些方法的时候也会触发视图的更新。
新建一个以Array.protoType为原型的对象。把这个对象本身附加属性,再将这个新建的对象作为原型或者作为属性传给Observer的value中去,来达到监听变化的目的。
判断当前的执行环境是否支持proto,支持则直接指向这组方法,如果不支持,则遍历这组方法,添加到对象中
Proxy和Object.defineProperty的优劣势
- proxy能直接监听对象,而不是属性
- proxy能直接监听数组
- proxy返回的是一个新对象,我们可以直接操作新的对象达到目的,而object.defineProperty只能遍历对象属性进行修改
- proxy有很多种拦截方法,但是object.defineProperty不具备
- object.defineProperty的兼容性比proxy好一些
虚拟DOM的实现原理(juejin.cn/post/684490…
用JavaScript对象来模拟真实的DOM树,对真实的DOM树进行抽象
diff算法 ----> 比较两个虚拟DOM树的差异
pach算法 ----> 将两个虚拟DOM对象的差异应用到真正的DOM树
减少真实DOM的创建,避免真实DOM的对比,取而代之的是通过对两个虚拟DOM对象的对比,对象的对比性能要远远强于DOM的对比
虚拟DOM的优缺点
优点:
- 保证性能的下限:框架的虚拟DOM需要适配上层所有API的操作,因此虚拟DOM操作的实现必须是普适的,所以它的性能也并不是最优的,但是相对于粗暴的DOM操作性能还是要好很多。因此虚拟DOM可以保证我们在不需要手动优化的情况下,提供不错的性能
- 不需要手动操作DOM:有了虚拟DOM之后,我们不再需要手动的去操作DOM,只需要维护view-model的交互逻辑即可。虚拟DOM和数据双向绑定,足以帮我们以预期的方式更新页面,大大的提高了开发的效率
- 跨平台:虚拟DOM的本质就是JavaScript对象,而真实的DOM和平台具有强相关,所以虚拟DOM可以更方便的完成跨平台操作
缺点:
不能极致优化:
虽然虚拟DOM+合理的优化足以应对绝大多部分的应用的性能,但是对于性能要求极高的应用,虚拟DOM无法完成极致优化
VUE中的key有什么作用
www.zhihu.com/question/61…
概念:
key是vue中vnode的唯一标记,通过这个key,我们的diff操作可以更准确,更快速
当用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key的作用主要是为了高效的更新虚拟DOM。
虚拟DOM中diff是一层层的对比,这个对比的过程中需要有一个名字相对应,这样才能更精准的知道对比的是同一个元素。key值不用index,因为当我们的数组发生变化时,index的值也会变化,最好使用id这类唯一且稳定的标识
router 的区别:
- $route 是‘路由信息对象’,包含params、hash、path、name、query等
- $router是路由实例,包含钩子函数和跳转方法
路由的钩子函数
首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整页面的重定向功能。
beforeEach主要有3个参数to,from,next: - to:route即将进入的目标路由对象,
- from:route当前导航正要离开的路由
- next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。
next Tick
当你修改了data的值然后马上获取这个dom元素的值,是不能获取到更新后的值的。需要使用$nextTick这个回调,让修改后的data值渲染更新到dom元素之后在获取,才能成功
原理
根据执行环境分别尝试采用Promise、MutationObserver、setImmediate,如果以上都不行则采用setTimeout
VUE3相对于VUE2做了哪些事情:
- proxy替换object.defineProperty
- 虚拟DOM的瓶颈,静态节点标记
- 用户应该能够在构建时删除未使用的框架功能的代码-也称为“Tree Shaking”
- 语言切换至typeScript
- 整体体积更小
- composition API
- VUE3.0相比VUE2.x在diff上做了哪些改动
- Vue3.0在创建vnode的时候就确定其类型,已经在mount/patch的过程中采用位算法来判断一个VNode的类型,在这个基础上配合原来的diff核心算法,性能上有了很大的提升
VUE3.0的数据响应的原理?
- Proxy只代理对象的第一层,如何深度观测?如果防止监测数据时多次set、get
- proxy完成数据响应
- 判断Reflect.get的返回值是不是引用类型,如果是的话则使用 .reacitve进行深度检测
- 判断key值是否为当前被代理对象的target的自身属性或判断新值和旧的值是否相等,满足其中之一才可能执行trigger
VUE的性能优化
- 保证key的稳定性和唯一性,避免使用index
- 减少data中的数据,data中的数据都会增加getter和setter,收集对应的watcher
- v-if和v-for避免同时使用
- 尽量用v-if去代替v-show
- 采用keep-alive进行页面缓存(可以减少虚拟DOM的对比产生的性能损耗)
- 第三方模块按需引入
- 服务端渲染SSR(将标签渲染成HTML的过程交给服务端去处理)
- 防抖和节流
- 代码压缩
- 使用cdn加载第三方模块
- sourceMap优化