html + css基础
1、CSS 盒子模型,绝对定位和相对定位:
内容(content)、填充(padding)、边界(margin)、 边框(border).
position: absolute;绝对定位:绝对定位是相对于元素最近的已定位的祖先元素(即是设置了绝对定位或者相对定位的祖先元素)。如果元素没有已定位的祖先元素,那么它的位置则是相对于最初的包含块(body)。
绝对定位与文档流无关,所以它们可以覆盖页面上其他的元素,可以通过z-index属性来控制这些层的对方顺序。
position: relative;相对定位:相对定位是相对于元素在文档中的初始位置;
注意,在使用相对定位时,无论是否进行移动,元素仍然占据原来的空间。因此,移动元素会导致它覆盖其它框。
2、清除浮动,什么时候需要清除浮动,清除浮动都有哪些方法
背景不能显示;边框不能撑开;margin设置值不能正确显示
方法一:添加新的元素,应用 clear:both;
<div class="outer">
<div class="div1">1</div>
<div class="div2">2</div>
<div class="div3">3</div>
<div class="clear"></div>
</div>
.outer{ border:1px solid #ccc; background:#fc9; color:#fff; margin:50px auto; padding:50px;}
.div1{ width:80px; height:80px; background:#f00; float:left; }
.div2{ width:80px; height:80px; background:blue; float:left; }
.div3{ width:80px; height:80px; background:sienna; float:left; }
.clear{ clear:both; height:0; line-height:0; font-size:0; }
方法二:父级div定义 overflow:auto;
<div class="outer over-flow">
<div class="div1">1</div>
<div class="div2">2</div>
<div class="div3">3</div>
</div>
.outer{ border:1px solid #ccc; background:#fc9; color:#fff; margin:50px auto; padding:50px;}
.div1{ width:80px; height:80px; background:#f00; float:left; }
.div2{ width:80px; height:80px; background:blue; float:left; }
.div3{ width:80px; height:80px; background:sienna; float:left; }
.over-flow{ overflow:auto; zoom:1; } //zoom:1;是在处理兼容性问题
方法三:据说最高大上的方法,:after方法,作用于浮动元素的父亲
.outer:after { clear:both;content:'';display:block;width:0;height:0;visibility:hidden; }
3. 如何保持浮层水平垂直居中
(1)利用绝对定位和transform
.children{
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%,-50%);
background: black;
}
(2) 利用flexbox
.parent{
justify-content:center;
align-items:center;
display: -webkit-flex;
}
(3)已知元素高度,手动计算中间的位置距离
(4) 利用定位与margin: auto;
4. position 和 display 的取值和各自的意思和用法
1、position属性取值:static(默认)、relative、absolute、fixed(定位原点相对于浏览器窗口)、inherit。
2、display属性取值:none(隐藏,不占据空间)、inline、inline-block(行高可变)、block、flex、inherit
5. 样式的层级关系,选择器优先级,样式冲突,以及抽离样式模块怎么写
一个是权重,另一个是共用样式和私用样式
(!important>)id选择器>class选择器(属性选择器/伪类选择器)>标签选择器(伪元素选择器) 同类选择符条件下层级越多的优先级越高。优先级就近原则,同权重情况下样式定义最近者为准
选择器一样的情况下后面的会覆盖前面的属性
因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面显示差异。当然,初始化样式会对SEO有一定的影响,但鱼和熊掌不可兼得,但力求影响最小的情况下初始化。
6、CSS3中新增伪类举例
:enabled | 控制表单控件的可用状态。 |
:disabled | 控制表单控件的禁用状态。 |
:checked | 单选框或复选框被选中。 |
7、px和em和rem的区别,CSS中link 和@import的区别
像素px是相对于显示器屏幕分辨率而言;em相对于当前对象内文本的字体尺寸;rem相对的是HTML根元素
link属于XHTML标签,而@import完全是css提供的一种方式;link引用的CSS会同时被加载,而@import引用的CSS会等到页面全部被下载完再加载;用JavaScript控制dom去改变样式的时候,只能使用link标签
8、flex
它的所有子元素自动成为容器成员,称为Flex项目(flex item),简称”项目”。
容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)
JavaScript 基础
1、JavaScript 里有哪些数据类型,解释清楚 null 和 undefined,解释清楚原始数据类型和引用数据类型。比如讲一下 1 和 Number(1)的区别
在es6中大致可以认为有7中数据类型:null 、undefined、string、number、boolean、Symbol、object
null用来表示尚未存在的对象;undefined表示"缺少值",就是此处应该有一个值,但是还没有定义
undefiend\null\boolean\string\number为原始类型;object(包括function、array、Date...)为引用类型
1 是一个原始定义好的的number类型,Number(1)是一个函数类型
2、prototype 是什么东西,原型链的理解,什么时候用 prototype
prototype是函数对象上面预设的对象属性
function foo(){};
foo.prototype.z = 3;
var obj = new foo();
obj.z // 3
通过new构造对象(实例)的特点是,obj的原型(prototype)指向了构造器的prototype属性,也就是foo.prototype,而foo.prototype则指向了原始的Object.prototype,Object.prototype也有原型,为null。这就是一整个原型链。
3、JS 怎么实现一个类。怎么实例化这个类
JavaScript使用函数来定义类
创建对象(即类的实例化)使用 new 关键字
比如,上面的foo就可以理解为一个类,obj可以理解为类的一个实例化(对象)
4、理解闭包吗?请讲一讲闭包在实际开发中的作用;闭包建议频繁使用吗?
闭包简单来说就是函数中的函数,也可以把它理解为一种现象,就是说一个函数要访问另外一个目标函数内部的变量,就要在目标函数中再定义一个函数(以此来把作用域链往下延长一段,目的就是为了利用js在找自由变量时,会沿着作用域链一级一级往上找的特点),并将这个定义的函数return出来,供外部使用。在实际开发中,闭包主要是用来封装变量,收敛权限
好处数据隐藏和封装,但是在处理速度和内存消耗方面对脚本性能具有负面影响
5、apply()与call()的区别
每个函数都包含两个非继承而来的方法: appy()和call()。这两个方法的用途都是在特定的作用域中调用函数,然后可以设置调用函数的this指向。
call()的使用和apply()基本是一样的,不同的是参数的传递,call()必须明确的传入每一个参数。
6、Javascript类与继承
第一种借助构造函数实现继承;
function Parent1 () {
this.name = 'parent1';
}
function Child1 () {
Parent1.call(this); //这里的call用apply也可以
this.type = 'child1';
}
console.log(new Child1());
第二种是借助原型链实现继承;
function Parent2 () {
this.name = 'parent2';
this.play = [1, 2, 3];
}
function Child2 () {
this.type = 'child2';
}
Child2.prototype = new Parent2(); //通过把Child2的原型指向Parent2来实现继承
第三种 组合方式
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
function Child3 () {
Parent3.call(this); //子类里执行父类构造函数
this.type = 'child3';
}
Child3.prototype = new Parent3(); //子类的原型指向父类
//以下是测试代码
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);
7、let、const、var
let:块级作用域;不存在变量声明提前;不能重复定义
const:声明的常量地址是不允许改变
var: js用来声明变量的方法
8、promise
链式操作:
function runAsync(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('执行完成');
resolve('随便什么数据');
}, 2000);
});
return p;
}
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return runAsync3();
})
.then(function(data){
console.log(data);
});
9、async/await
function fn1 () {
console.log('Function 1')
}
function fn2 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Function 2')
resolve()
}, 500)
})
}
function fn3 () {
console.log('Function 3')
}
async function asyncFunArr () {
fn1()
await fn2()
fn3()
}
asyncFunArr()
// output =>
// Function 1
// Function 2
// Function 3
10、写一个方法遍历所有文档树所有节点
非递归:
function traversalUsingNodeIterator(node){
var iterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT,null,false);
var node = iterator.nextNode();
while(node != null){
console.log(node.tagName);
node = iterator.nextNode();
}
}
递归:
function traversal(node){
//对node的处理
if(node && node.nodeType === 1){
console.log(node.tagName);
}
var i = 0, childNodes = node.childNodes,item;
for(; i < childNodes.length ; i++){
item = childNodes[i];
if(item.nodeType === 1){
//递归先序遍历子节点
traversal(item);
}
}
}
11、cookie和webstorege
cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递
cookie数据还有路径(path)的概念,可以限制。cookie只属于某个路径下、
cookie数据不能超过4K,webStorage可以达到5M或更大
数据的有效期不同
sessionStorage:仅在当前的浏览器窗口关闭有效;
localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;
cookie:只在设置的cookie过期时间之前一直有效,即使窗口和浏览器关闭
作用域不同
sessionStorage:不在不同的浏览器窗口中共享,即使是同一个页面;
localStorage:在所有同源窗口都是共享的;
cookie:也是在所有同源窗口中共享的
webStorage的API借口使用更方便
webstorege所有对象会转成字符串
12、jsonp和跨域
协议、域名、端口相同则视为同源,域名也可换成IP地址,不同源的页面脚本不能获取对方的数据
在html元素中拥有src属性的元素是可以跨域访问资源,页面中的<script><img><iframe>不受到同源策略限制
JSONP(JSON with padding)的理念就是,与服务器约定好一个回调函数名,服务端接收到请求之后,返回一段Javascript,这段Javascript中调用了约定好的回调函数,并且将数据作为参数进行传递,页面接收到这段Javascript后就会执行这段回调函数,此时数据已传到客户端
JSONP缺点:只支持GET请求,不支持POST或其他的HTTP请求
13、sort排序相关
数组会按照字符的Unicode进行排序(把数组里面当成字符串处理)
sort()方法是使用的冒泡和插入两种方式结合进行排序的
14、数组和对象的深浅拷贝
数组深拷贝:
遍历复制
slice(0)巧办法进行修改,返回的是一个副本
concat()
对象深拷贝:
JSON.parse(JSON.stringify(a))
递归调用浅拷贝:
var deepCopy = function(obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
// 根据obj的类型判断是新建一个数组还是一个对象
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
// 遍历obj,并且判断是obj的属性才拷贝
if (obj.hasOwnProperty(key)) {
// 判断属性值的类型,如果是对象递归调用深拷贝
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
15、String + Array的一些基本操作
string:
substring(start开始位置的索引,end结束位置索引) 截取字符串,截取的位置不包含结束位置的字符,只写start表示从开始位置截取到最后,end>start时,会默认以小的参数为开始位置的索引
slice(start开始位置索引,end结束位置索引)截取字符串,当end>length时,返回空字符串""
substr(start开始位置索引,end需要返回的字符个数)截取字符串,只传入start时,默认从start开始截取到最后一个
charAt(index),返回指定index处的字符,但当index为负数或者大于字符串长度时,返回空字符串""
indexOf(string)该方法返回字符串内第一次出现子字符串的位置。如果没有找到子字符串,则返回-1。
lastIndexOf(string) 倒叙查找
split(separator分隔符,howmany分割返回数量)用于方法用于把一个字符串分割成字符串数组
replace(regexp,replacement)用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串
search(regexp)用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。返回第一个与 regexp 相匹配的子串的起始位置
match(regexp/str) 该方法用于在字符串内检索指定的值,或找到一个或多个正则表达式的匹配,并返回一个存储了该值的数组
toLowerCase/toUpperCase
concat() 该方法用于连接两个或多个字符串
array:
push() 向数组的末尾添加一个或更多元素,并返回新的长度
unshift() 向数组的开头添加一个或更多元素,并返回新的长度
shift() 删除并返回数组的第一个元素
pop() 删除并返回数组的最后一个元素
join() 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔
toString() 该方法可把数组转换为字符串,返回值与没有参数的 join() 方法返回的字符串相同
concat() 该方法可以连接两个或更多的数组,并返回结果
slice(start,end) 该方法可从已有的数组中返回选定的元素。(返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。),不传参返回整个数组,传入负值(等价于负值+数组长度的位置开始)
splice() 参数1 添加/删除 的起始位置,参数2 要删除的项目数量。如果设置为 0,则不会删除项目,参数3 向数组添加的新项目。(可以添加多个)
reverse() 该方法用于颠倒数组中元素的顺序。并返回颠倒顺序后的数组
sort() 该方法用于对数组的元素进行排序,会直接对Array进行修改,它返回的结果仍是当前Array
reduce() 把结果继续和序列的下一个元素做累积计算,[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
map(fuc) 数组中的每一个值都进行fuc,返回对应的新数组,
filter() 和map()不同的是,传入的函数依次作用于每个元素后,根据返回值是true还是false决定保留还是丢弃该元素
16、数组去重的方法
function unique(arr) {
const res = new Map();
return arr.filter((a) => !res.has(a) && res.set(a, 1))
// 或者
return Array.from(new Set(arr))
}
17、冒泡和捕获
事件流描述的是从页面中接受事件的顺序
冒泡:事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点
捕获:不太具体的DOM节点应该更早接收到事件,而最具体的节点应该最后接收到事件
16、事件委托和事件绑定
不需要为每一个元素都添加监听事件而是通过委托给父元素来处理
可以方便动态添加元素。不需要再为新添加的元素重新绑定事件
17、箭头函数的this指向问题
普通函数中的this表示调用此函数时的对象
箭头函数中会往上寻找this,直到找到所代表的this为止
18、变量提升和函数提升
在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域。变量提升即将变量声明提升到它所在作用域的最开始的部分
console.log(global); // undefined
var global = 'global';
console.log(global); // global
js中创建函数有两种方式:函数声明式和函数字面量式。只有函数声明才存在函数提升!
console.log(f1); // function f1() {}
console.log(f2); // undefined
function f1() {}
var f2 = function() {}
19、高阶函数
JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数
function add(x, y, f) {
return f(x) + f(y);
}
add(-5, 6, Math.abs);
map,reduce,filter,sort都是高阶函数
20、setTimeout、setInterval和requestAnimationFrame
setTimeout()(设置某个时间后执行某个动作,表示延时执行某个动作)
setInterval()(设置每隔多久执行一次某个动作,它是循环的,如果想重复执行使用该方法)
requestAnimationFrame()(不需要设置时间间隔)
三个方法在做动画时会经常用到
21、模块化开发
通行的js模块规范主要有两种:CommonJS
和AMD
ES6:
export:它是用来定义模块的,可以导出对象、函数、类、字符串等等:
export和export default的区别就是:前者导出的东西需要在导入的时候加{},而后者则不需要
import导入的也可以通过as改名
动态引入:其实就是基于promise语法,根据promise机制来实现动态引入。有了这种机制就可以实现按需加载
// 1.html
<script type="module">
import('./1.js').then(res =>{
console.log(res.a); // 1
});
</script>
22、引起内存泄漏的原因
(1)、创建意外的全局变量;
function leaks(){
a = 3;
}
(2)、element元素被闭包引用,因此无法减少对element的引用数
function assignHandler(){
let element = document.getElementById("someElement");
element.onclick = function () {
alert(element.id);
};
}
解决:
function assignHandler(){
let element = document.getElementById("someElement");
let id = element.id;
element.onclick = function () {
alert(id);
};
element = null;
}
(3)、循环引用 在引用计数策略下会导致内存泄漏,标记清除不会
function fn() {
var a = {};
var b = {};
a.pro = b;
b.pro = a;
}
fn();
在引用计数策略下,a和b的引用次数不为0,不会被垃圾回收器回收内存。如果fn函数被大量调用,就会造成内存泄漏
(4)、当页面中元素被移除或替换时,若元素绑定的事件仍没被移除,在IE中不会作出恰当处理,此时要先手工移除事件,不然会存在内存泄露
<div id="myDiv">
<input type="button" value="Click me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
btn.onclick = null;// 按钮存在事件未处理
document.getElementById("myDiv").innerHTML = "Processing...";
}
</script>
23、Map和Set
map:初始化map并通过key来取value
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95
set:存储的是一组不重复的key.
var s=new Set([1,2,3,4,4]); //Set(4) {1, 2, 3, 4}
框架基础
1、vue数据绑定原理
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的
var vm = new Vue({
data: {
obj: {
a: 1
}
},
created: function () {
console.log(this.obj);
}
});
每个数据属性有两个相对应的get和set方法,因为vue是通过Object.defineProperty()来实现数据劫持的(Object.defineProperty( )可以来控制一个对象属性的一些特有操作,比如读写权、是否可以枚举)
2、vue父子组件和兄弟组件的通信问题
父组件到子组件通信:使用props
子组件到父组件的通信:可以在子组件中使用$emit触发事件的值传出去,父组件通过事件监听,获取数据
可以由子组件内部主动传数据给父组件,由父组件监听接收(在子组件中定义一个click点击事件来触发自定义事件$emit,然后 在父组件监听)
也可以通过父组件决定子组件什么时候传值给父组件,然后再监听接收(是在父组件中定义一个click点击事件,在组件中通过 refs获取实例方法来直接触发事件,然后在父组件中监听)
兄弟组件之间的通信:
创建一个新Vue的实例,让各个兄弟共用同一个事件机制
传递数据方,通过事件触发$emit(方法名,传递的数据)
接收数据方,在mounted()钩子函数(挂载实例)中 触发事件$on(方法名,callback(接收数据的数据)),此时callback函数中的 this已经发生了改变,可以使用箭头函数
//建立一个空的Vue实例,将通信事件挂载在该实例上
//emptyVue.js
import Vue from 'vue'
export default new Vue()
//兄弟组件a:childa.vue
<template>
<div>
<span>A组件->{{msg}}</span>
<input type="button" value="把a组件数据传给b" @click ="send">
</div>
</template>
<script>
import vmson from "./emptyVue"
export default {
data(){
return {
msg:"我是a组件的数据"
}
},
methods:{
send:function(){
vmson.$emit("aevent",this.msg)
}
}
}
</script>
//兄弟组件b:childb.vue
<template>
<div>
<span>b组件,a传的的数据为->{{msg}}</span>
</div>
</template>
<script>
import vmson from "./emptyVue"
export default {
data(){
return {
msg:""
}
},
mounted(){
vmson.$on("aevent",(val)=>{//监听事件aevent,回调函数要使用箭头函数;
console.log(val);//打印结果:我是a组件的数据
this.msg = val;
})
}
}
</script>
//父组件:parent.vue
<template>
<div>
<childa><childa>
<childb></childb>
</div>
</template>
<script>
import childa from "./childa";
import childb from "./childb";
export default {
data(){
return{
msg:""
}
},
components:{
childa,
childb
},
}
</script>
3、vuex的原理
- store是怎么注册的?
vuex在vue 的生命周期中的初始化钩子前插入一段 Vuex 初始化代码。给 Vue 的实例注入一个 $store
的属性,我们在 Vue 的组件中可以通过 this.$store.xxx
访问到 Vuex 的各种数据和状态
- mutation,commit 是怎么实现的?
registerMutation 是对 store 的 mutation 的初始化,它接受 4 个参数,store为当前 Store 实例,type为 mutation 的 key,handler 为 mutation 执行的回调函数,path 为当前模块的路径。mutation 的作用就是同步修改当前模块的 state ,函数首先通过 type 拿到对应的 mutation 对象数组, 然后把一个 mutation 的包装函数 push 到这个数组中,这个函数接收一个参数 payload,这个就是我们在定义 mutation 的时候接收的额外参数。这个函数执行的时候会调用 mutation 的回调函数,并通过 getNestedState(store.state, path) 方法得到当前模块的 state,和 playload 一起作为回调函数的参数
commit 支持 3 个参数,type 表示 mutation 的类型,payload 表示额外的参数,根据 type 去查找对应的 mutation,如果找不到,则输出一条错误信息,否则遍历这个 type 对应的 mutation 对象数组,执行 handler(payload) 方法,这个方法就是之前定义的 wrappedMutationHandler(handler),执行它就相当于执行了 registerMutation 注册的回调函数
- 辅助函数是怎么实现的?
mapState在调用了 normalizeMap 函数后,把传入的 states 转换成由 {key, val} 对象构成的数组,接着调用 forEach 方法遍历这个数组,构造一个新的对象,这个新对象每个元素都返回一个新的函数 mappedState,函数对 val 的类型判断,如果 val 是一个函数,则直接调用这个 val 函数,把当前 store 上的 state 和 getters 作为参数,返回值作为 mappedState 的返回值;否则直接把 this.$store.state[val] 作为 mappedState 的返回值
4、vuex的action和mutation的异步操作和同步操作问题
5、vue的事件监听
可以使用 v-on
指令来绑定并监听 DOM 事件。绑定的内容可以是一个当前实例上的方法 (后面无需跟括号) 或一个内联表达式。如果提供的是一个方法,则原生的 DOM event 会被作为第一个参数传入,同时这个 event 会带有 targetVM
属性,指向触发该事件的相应的 ViewModel
6、vue-router获取自定义参数
设置好路由配置
router.map({
'/history/:deviceId/:dataId': {
name: 'history', // give the route a name
component: { ... }
}
})
在v-link中传递参数
<a v-link="{ name: 'history', params: { deviceId: deviceId, dataId: dataId }}">history</a>
7、vue-router的go相关
router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)
router.push(location)
想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
router.replace(location)
跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
8、vue组件设计相关
页面上把每个独立可以交互的区域视为一个组件
每个组件对应一个工程目录,组件所需要的各种资源在这个目录下就近维护
页面不过是组件的容器,组件可以嵌套自由组合形成完整的页面
9、模块化的工具
vue-cli
10、webpack打包优化
查看项目打包
webpack外部扩展
总结需要抽离的公共依赖
使用CDN引入资源
配置webpack.conf.js
11、mvvm的好处
(1)、数据一致性,因为MVVM采用ViewMode进行数据的自动更新,数据变化时无需进行各种传递,并且可以做到各个Fragment之间数据共享
(2)、解耦性,将View和Mode解耦,View层真正只负责UI相关的,Mode负责数据相关的,不会造成庞大的控制逻辑,特别逻辑多了会出现超级Activity
(3)、利于单元测试,因为View层和Mode层是解耦的,所以可以单独对View和Mode逻辑进行单元测试
12、lodash相关
Lodash是一套工具库,它内部封装了诸多对字符串、数组、对象等常见数据类型的处理函数,其中部分是目前ECMAScript尚未制订的规范,但同时被业界所认可的辅助函数
13、vue、angular7和react的对比
1 vue
优点: 入门非常容易,资料丰富,框架功能完善,加入非常多的特性,例如,if, for, async,为开发者节省很多垃圾代码。模板支持html和jsx,支持自定义指令,方便操作dom的一致行为。
缺点: 插件引入不够规范,代码提示变弱,不利于维护。开发规范不清晰,项目庞大之后组织结构混乱,不利于新人接手旧的项目。
低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
· 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
· 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
· 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
2 react
优点: 框架功能强大,非常灵活,组建非常规范,jsx功能强大,生态不错。文档也不错。
缺点:无数个 回调,无数个选择表达式,this绑定,没有if没有for指令,更没有父子状态同步,需要自己写回调同步状态,这简直就是垃圾中的战斗机。因为太灵活,写代码也是毫无逻辑,乱写一通,项目混乱堪比vue更甚。维护起来,让人直接放弃。
3 angular7
优点: 组织结构清晰,组建结构规范,官方支持各种技术,不用去找。依赖注入强大的无解,保证代码运行的可控性。对ts深度集成,更是如虎添翼。框架搭建完毕,之后组建拼接,非常高效。
缺点: 入门太难,有些技术不行的人真的很难。
总结: 如果你最擅长react,不用再考虑什么,就用react,如果你最擅长vue,也不用考虑,就选vue吧,不要被框架束缚。好与不好都由使用者决定。至于,angular,如果你的项目可能要好几年的迭代,那不用犹豫了,就选angular,这是你入坑的唯一理由,因为需要维护,不想加班熬夜的修bug,就用anglar。