- mvc 、mvp、mvvm区别
- Padding 和 margin 有什么不同?
- vw与百分比的区别?
- 设置小于12px字体尺寸?
- content-box和border-box区别
- let、const、var的区别
- 什么是变量提升
- 深copy 和 浅copy的区别
- css文本多行或单行显示,超出显示...
- 什么是数据类型隐式转换
- Set、map、object、weakMap 有什么区别
- Object
- forEach,map,reduce,some,every, filter,find,findIndex,includes使用和区别
- some()
- every() ---- 与some相反
- filter()
- find()、findIndex()
- 关于闭包
- vue的双向绑定原理
- 单页应用与多页应用的区别
mvc 、mvp、mvvm区别
(1) mvc 模式分为三个部分:
- 视图(View):用户界面
- 控制器(Controller):业务逻辑
- 模型(Model):数据保存
各部分通信如下:
- View 传送指令到 Controller
- Controller 完成业务逻辑后,要求 Model 改变状态
- Model 将新的数据发送到 View,用户得到反馈
所有通信都是单向的
用户可以直接向view发送指令(Dom事件),用户也可以直接向controller发送指令(改变 URL 触发 hashChange 事件)
(2) MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。
- 各部分之间的通信,都是双向的。
- View 与 Model 不发生联系,都通过 Presenter 传递。
- View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。
(3) MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。
唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。Angular 和 Ember 都采用这种模式。
- MVVM 是 Model-View-ViewModel 的缩写。
- Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。
- View 代表UI 组件,它负责将数据模型转化成UI 展现出来。
- ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。
- 在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
- ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
Padding 和 margin 有什么不同?
- padding内边距,margin外边距
- 作用对象不同,padding针对自身,margin作用于外部对象
vw与百分比的区别?
- vw根据视图窗口,百分比根据父元素
设置小于12px字体尺寸?
transform: scale(0.8);
-webkit-transform: scale(0.8);
content-box和border-box区别
- content-box最大的特点是计算盒子宽高时会把padding和border的长度算进来(外边距不会算进来)。
- border-box则是你宽和高设置为多少,则这个盒子的宽高就是多少,如果设置padding和border,相应内容区域就会减少。
let、const、var的区别
- var是函数作用域,而let是块作用域。在函数内声明了var,整个函数内都是有效的,在for循环内定义了一个var变量,实际上其在for循环以外也是可以访问的,而let由于是块作用域,所以如果在块作用域内(for循环内)定义的变量,在其外面是不可被访问的。
- var可以允许重复声明相同的变量,后者会覆盖前者,let则不能重复声明相同的变量。
- var声明的变量有
变量提升
特性,let声明则没有这个特性 - const 声明一个只读的常量,一旦声明,常量值就不可以改变。所以const一旦声明就必须立即初始化。只声明,不赋值,会报错(注意:var let 只声明不赋值 : undefined),不能重新赋值const定义的值,但是可以修改const声明的对象类型。
eg:
const a = {name: 'aaa'}
a.name = 'bbb'
console.log(a.name) bbb
const a = {name: 'aaa'}
a={name: 'ccc'}
console.log(a)
VM333:2 Uncaught TypeError: Assignment to constant variable.
因为const 保存的是指向的数组活对象的指针,对于基本类型,被const声明的变量是不可以修改的,但是对于对象或者数组,指针依然不能修改,但是指针指向的对象或者数组可以修改。
什么是变量提升
- javascript引擎的工作方式,是先解析代码,获取所有被声明的变量,然后在一行一行的运行。这造成的结果就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升。
示例:
function fun() {
console.log(a)
var a = 12
}
fun()
// 输出结果undefined
原因:就是js在创建执行上下文时,会检查代码,找出变量声明和函数声明,并将函数声明完全存储在环境中,而将通过var声明的变量设定为undefined,这就是所谓的变量提升。从字面上理解就是变量和函数声明会被移动到函数或者全局代码的开头位置。
深copy 和 浅copy的区别
深copy和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
假设B复制了A,修改A的时候,看B是否发生变化:
- 如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
- 如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存
实现深copy
// 自定义
function copy (obj) {
let newObj = null
if (typeof obj === 'object' && obj !== null) {
newObj = obj instanceof Array ? [] : {}
for (let i in obj) {
newObj[i] = typeof obj[i] === 'object' ? copy(obj[i]) : obj[i]
}
} else {
newObj = obj
}
return newObj
}
//es6
JSON.parse(JSON.stringify(arr))
// lodash
clone.deep()
css文本多行或单行显示,超出显示...
单行
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
多行,比如2行
-webkit-line-clamp: 2;
overflow: hidden;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
什么是数据类型隐式转换
在js中,当运算符在运算时,如果两边数据不统一,CPU 就无法计算,这时我们编译器会自动将运算符两边的数据做一个数据类型转换,转成一样的数据类型再计算。比如:1 > "0"这行代码在js中并不会报错,编译器在运算符时会先把右边的 "0" 转成数字 0 然后在比较大小。
一、JS中的数据类型隐式转换的三种情况
- 转换为boolean类型
- 转换为number类型
- 转换为string类型
二、js中数据类型转换的规则
转为Boolean类型
数据在 逻辑判断 和 逻辑运算 之中会隐式转换为boolean类型
数据类型 | 转换为true的值 | 转换为false的值 |
---|---|---|
String | 任何非空字符串 | 空字符串(""或’') |
Number | 任何非零数字 | 0和NaN |
Object | 任何对象 | null |
Undefined | 不适用 | undefined |
连续使用两个非操作符(!!)可以将一个数强制转换为boolean类型
转换为 string 类型
原始数据类型 | 转换之后的值 |
---|---|
数字类型 | 数字类型的字符表示 |
null | ‘null’ |
undefined | ‘undefined’ |
布尔类型 | true变’true’,false变’false’ |
转换为 number 类型
原始数据类型 | 转换之后的值 |
---|---|
空字符 ''或"" | 0 |
非空字符串 | 将字符内的数据内容变为数据,如果还有其他符号中文等转为NaN |
true | 1 |
false | 0 |
null | 0 |
undefined | NaN |
NaN | (不用转,typeof NaN 得到"number") |
Set、map、object、weakMap 有什么区别
Set
Set是唯一值的集合,类似与数组
一个Set可以容纳任何数据类型的任何值
每个值在Set中只出现一次,因此常用做数组去重
- new Set() 创建新的 Set 对象。
- add() 向 Set 添加新元素。
- clear() 从 Set 中删除所有元素。
- delete() 删除由其值指定的元素。
- has() 如果值存在则返回 true。
- forEach() 为每个元素调用回调。
- keys() 返回 Set 对象中值的数组。
- size 返回元素个数。
let arr = [1,2,3,4,5,5]
let setObj = new Set(arr)
// new Set() Array 转Set
// print:Set(5) {1, 2, 3, 4, 5}
setObj.add(10)
// add 添加新元素
// print:Set(5) {1, 2, 3, 4, 5, 10}
...
Map
Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
Map 对象记得键的原始插入顺序。
Map 对象具有表示映射大小的属性。
Map 对象的几个常用方法和属性
new Map() 创建新的 Map 对象。
set() 为 Map 对象中的键设置值。
get() 获取 Map 对象中键的值。
entries() 返回 Map 对象中键/值对的数组。
keys() 返回 Map 对象中键的数组。
values() 返回 Map 对象中值的数组。
Object
object 和 map区别
- Object的键只能是字符串或者Symbol,而Map的键可以是任意值。
- Map中的键值是有序的(FIFO),而Object中的键是无序的。
- Map中的键值个数可以从size属性中获取,而Object中的键值只能手动获取。
forEach,map,reduce,some,every, filter,find,findIndex,includes使用和区别
forEach()
- 遍历数组全部元素(自动遍历数组length次),
无法break中途跳出循环 ,不支持return操作退出循环
不产生新数组
map()
返回新数组,不改变原数组
无法break中途跳出循环 ,不支持return操作退出循环
- 回调函数参数:item(数组元素)、index(序列)、arr(数组本身)
使用return操作输出,会循环数组每一项,并在回调函数中操作
reduce() ---- 叠加器
不一定在数学意义上的叠加计算,这里叠加指:让数组中的前项和后项做某种计算,并累计最终值。
特点:
创建新数组,不改变原数组
无法break中途跳出循环 ,不支持return操作退出循环
输出新数组(return叠加什么就输出什么)
- 回调函数参数(与其他的不一样)
pre(第一次为数组第一项,之后为上一操作的结果)
next(数组的下一项)
index(next项的序列)
arr(数组本身)
回调函数后的改变第一项参数。(不影响原数组) 使用return操作输出,会循环数组每一项,并在回调函数中操作
let arr = [1,2,3,4,5,6]
let list = arr.reduce((sum, next, index) => {
return sum + next
}
)
console.log('arr:',arr)
console.log('list:', list)
//print:
//arr: (6) [1, 2, 3, 4, 5, 6]
//list: 21
some()
检测数组中是否有某些项符合条件,只要有一项符合了,马上return true,退出循环
特点:
不创建新数组 ,不改变原数组
-
当内部return true时退出循环
回调函数参数,item(数组元素)、index(序列)、arr(数组本身)
使用return操作输出,会循环数组每一项,并在回调函数中操作
let arr = [1,2,3,4,5,6]
arr.some((value, index) => {
if (value == 3){
return true
}
console.log(value)
}
)
//print: 1 2
every() ---- 与some相反
检测数组中的每一项是否符合条件,只要不符合就返回false,退出循环
特点:
不创建新数组 ,不改变原数组
当return false时退出循环( 需要写return true; )
- 回调函数参数,item(数组元素)、index(序列)、arr(数组本身)
- 使用return操作输出,会循环数组每一项,并在回调函数中操作
如果没写return true,就直接退出了
let arr = [1,2,3,4,5,6]
arr.every((value, index) => {
if (value == 3){
console.log(value)
return false
} else {
console.log(value)
return true
}
}
)
// print: 1 2 3
filter()
筛选出数组中符合条件的项 ,组成新数组
特点:
创建新数组 , 不改变原数组
输出的是 判断为true的数组元素 组成的新数组
- 回调函数参数:item(数组元素)、index(序列)、arr(数组本身)
- 使用return操作输出,会循环数组每一项,并在回调函数中操作
find()、findIndex()
- find() : 找元素(找出某个符合条件的元素)
- findIndex() : 找索引(找出某个符合条件的元素的索引)
find()
特点:
不创建新数组 ,不改变原数组
输出的是一旦 判断为true 则跳出循环输出符合条件的 数组元素(找到i一个后就不再往下找了)
- 回调函数参数,item(数组元素)、index(序列)、arr(数组本身)
- 使用return操作输出,会循环数组每一项,并在回调函数中操作
let arr = [1,2,3,4,5,6]
let list = arr.find((item, index) => {
return item > 2 && item < 5
}
)
console.log(list)
console.log(arr)
// print:
// 满足条件跳出循环,并打印value值 3
// list : [1, 2, 3, 4, 5, 6]
// arr: undefined
findIndex() ---- 特点与find() 基本相同
不创建新数组,不改变原数组
输出的是一旦判断为true 则跳出循环输出符合条件的 数组元素(找到i一个后就不再往下找了)
- 回调函数参数,item(数组元素)、index(序列)、arr(数组本身)
- 使用return操作输出,会循环数组每一项,并在回调函数中操作
let arr = [1,2,3,4,5,6]
let list = arr.find((item, index) => {
return item > 2 && item < 5
}
)
console.log(list)
console.log(arr)
// print:
// 满足条件跳出循环,并打印index 2
// list : [1, 2, 3, 4, 5, 6]
// arr: undefined
includes()
只是判断数组是否含有某值 ,不用return,不用回调函数,输出true或false
let arr = [1,2,3,4,5,6]
let value = arr.includes(3)
console.log(value)
// print: true
关于闭包
1、什么是闭包?
- 全局变量:在全局作用域下声明的变量。
- 局部变量:在局部作用域下声明的变量。
因为作用域链的存在,函数内部可以直接读取全局变量。而函数内部无法读取函数内部的局部变量。
闭包就是在一个函数作用域内创建另一个函数,这个内层函数可以访问外层函数变量,这个内层函数就是闭包。
2、闭包的特点
- 实现让外部访问函数内部变量;
- 变量会常驻在内存中;
- 可以避免使用全局变量,防止全局变量污染;
3、闭包有两个常用的用途
- 闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
- 闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
function A() {
let a = 1
function B() {
console.log(a)
}
B()
}
B() // 这个函数B就是一个闭包
// 调用方法A() 结果: print: 1
4、闭包缺点
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
vue的双向绑定原理
vue 在实例化的时候,使用 Object.definePropety() 方法或 Proxy 构造函数,对 data 进行 getter 和 setter 的处理。在组件渲染时,若用到 data 里的某个数据,这个数据就会被依赖收集进 watcher 里。当数据更新,如果这个数据在 watcher 里,就会收到通知并更新,否则不会更新——vue 采用“数据劫持”+“观察者模式(发布者-订阅者模式)”相结合的方式实现了双向绑定——vue 的响应式原理。
细说 vue 的双向绑定原理(vue 的响应式原理)
第一步,“数据劫持”:vue 2.x 用 Object.defineProperty() 方法来实现数据劫持,为每个属性分配一个订阅者集合的管理数组 dep;vue 3.x 用 ES6 的 Proxy 构造函数来实现数据劫持。
第二步,“添加订阅者”:在编译的时候在该属性的数组 dep 中添加订阅者,添加方式包括:v-model 会添加一个订阅者,{{}} 也会,v-bind 也会,只要用到该属性的指令理论上都会。
第三步,“为 input 添加监听事件”:为 input 添加监听事件,修改值就会为该属性赋值,触发该属性的 set() 方法,在 set() 方法内通知订阅者数组 dep,订阅者数组循环调用各订阅者的 update() 方法更新视图。
单页应用与多页应用的区别
单页面应用(SPA) | 多页面应用(MPA) | |
---|---|---|
组成 | 一个主页面和多个页面片段 | 多个主页面 |
刷新方式 | 局部刷新 | 整页刷新 |
url模式 | 哈希模式 | 历史模式 |
SEO搜索引擎优化 | 难实现,可使用SSR方式改善 | 容易实现 |
数据传递 | 容易 | 通过url、cookie、localStorage等传递 |
页面切换 | 速度快,用户体验良好 | 切换加载资源,速度慢,用户体验差 |
维护成本 | 相对容易 | 相对复杂 |
单页应用优缺点
- 优点:
具有桌面应用的即时性、网站的可移植性和可访问性
用户体验好、快,内容的改变不需要重新加载整个页面
良好的前后端分离,分工更明确 - 缺点:
不利于搜索引擎的抓取
首次渲染速度相对较慢