超级面试宝典🌻
导航大纲
- html小分队🌳
- css小分队🍉
- js小分队🐎
- var/let/const的区别
- JavaScript的参数是以什么方式进行传递的
- 说一下js的继承
- 说一下es6新增的特性
- async/await是什么
- 说一下promise
- 说一下promise的all方法
- 浏览器存储方式的应用场景
- 浏览器存储方式的区别
- jq中each和js中forEach的区别
- 说一下你对this的理解
- 用递归实现数组长度为5且元素的随机数在2-32间不重复的值
- 如何去掉字符串中的空格
- 删除字符串中最后一个字符
- 写一个方法把下划线命名转成驼峰命名
- JavaScript的回收机制
- 造成内存泄漏的操作有哪些
- 子函数在作用域内会不会被释放
- window对象和document对象有什么区别
- 写一个获取数组的最大最小值的方法
- 如何删除对象的a属性
- 说一下常用的数组方法
- typeof的返回值
- 说一下null和undefined的区别
- 说一下js的数据类型
- 浅拷贝和深拷贝的区别及如何实现深拷贝
- 请用一行代码实现深拷贝
- 适合用事件委托的事件有哪些
- 什么是事件委托
- 什么是冒泡事件
- 如何取消默认事件
- 如何防止冒泡和捕获事件
- 说一下js有哪些内置对象
- 说一下原型对象
- 说一下原型链
- 说一下作用域链
- toString和valueOf的区别
- Object是在堆还是栈里面
- 垃圾回收的两种方法
- 说一下闭包
- vue小分队🍖
- 小程序小分队👻
- http协议小分队🌐
- 其他小分队🔫
- 一些私有数据😈
html小分队🌳
html的元素有哪些
常用块级元素
- div(块级盒子)
- h1-h6(描述标题)
- p(段落)
- ul(无序列表)
- ol(有序列表)
- li(列表项)
- br(换行)
- form(表单)
- table(表格)
- hr(下划线)
常用行内元素
- span(行内盒子)
- a(链接)
- img(图片)
- button(按钮)
- input(输入框)
- select(下拉菜单)
- textarea(文本框)
h5系列
- header(头部块)
- section(内容块)
- footer(底部块)
- article(文章标签)
- audio(音频标签)
- video(视频标签)
页面导入样式的时候使用link和import有什么区别
- link是HTML标签,@import是css提供的。
- link引入的样式页面加载时同时加载,@import引入的样式需等页面加载完成后再加载。
- link没有兼容性问题,@import不兼容ie5以下。
- link可以通过js操作DOM动态改变样式,而@import不可以。
在页面上隐藏元素的方法有哪些
- display: none
- opacity: 0
- visibility: hidden
- z-index: -999
- transform: scale(0)
- margin-left: -100%
- position: relative; left: -100%
- width: 0; height: 0;
页面渲染html的过程
- 解析HTML文件,创建DOM树
- 解析CSS,形成CSS对象模型
- 将CSS与DOM合并,构建渲染树
- 布局和绘制
如何让a标签鼠标悬停变色
a:hover {
color: pink;
}
元素的alt和title有什么异同
alt作为图片的替代文字出现,title作为图片的解释文字出现
css小分队🍉
文本超出显示省略号
/* 多行显示省略号 */
.more_line {
overflow: hidden; /* 超出隐藏 */
text-overflow: ellipsis; /* 用省略号表示被截断的文本 */
display: -webkit-box; /* 设置弹性伸缩盒子 */
-webkit-box-orient: vertical; /* 设置伸缩盒子的子元素排列方式(从上到下垂直排列) */
-webkit-line-clamp: 2; /* 第几行开始显示省略号 */
}
/* 单行显示省略号 */
.one_line {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; /* 合并元素内的空白 */
}
说一下圣杯布局和双飞翼布局的理解和区别
作用
圣杯布局和双飞翼布局解决的问题是一样的
就是两边顶宽 中间自适应的三栏布局
中间栏要在 放在文档流前面 以优先渲染
区别
- 圣杯布局
为了中间div内容不被遮挡
将中间div设置了左右padding-left/padding-right
后
将左右两个div用相对布局position: relative
并分别配合right和left属性
以便左右两栏div移动后不遮挡中间div
- 双飞翼布局
为了中间div内容不被遮挡
直接在中间div内部创建子div用于放置内容
在该子div里用margin-left/margin-right
为左右两栏div留出位置
css3有哪些新增的特性
- 边框圆角
- 盒子阴影
- 文字阴影
- 2/3d变换
- 弹性盒子
- 过渡及自定义动画
具体说一下
- flexbox(弹性盒子)
- border-radius(边框圆角)
- border-image(边框图片)
- box-shadow(盒子阴影)
- text-shadow(文字阴影)
- background-size(背景图片的尺寸)
- transform(应用于元素的2D或3D转换)
- transition(过渡动画)
- animation(自定义动画)
position属性的作用
- static(默认值 标准文档流)
- relative(相对定位)
- absolute(绝对定位)
- fixed(固定定位)
- sticky(动态固定 relative/fixed的结合)
- inherit(继承父元素的position属性)
如何取消li的默认样式
.demo {
list-style: none;
}
清除浮动的方式及优缺点
- 在浮动元素后使用一个带clear属性的空元素(引入了冗余的元素)
- 用overflow: hidden清除浮动(有可能造成溢出元素不可见 影响展示效果)
- 用after伪元素清除浮动(bootstrap框架采用的清除浮动的方法)
说一下盒子模型的理解
css有两种盒子模型 分别是标准盒子模型和怪异盒子模型
- 标准盒子模型的宽度是content
- 怪异盒子模型的宽度是content + border + padding
.demo {
/* 标准盒子模型 */
box-sizing: content-box;
/* 怪异盒子模型 */
box-sizing: border-box;
}
使用flex实现一个上下左右居中的布局
.demo {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
css有哪些选择器
- 通配符
- id
- class
- 标签
- 后代选择器
- 子选择器
- 兄弟选择器
- 属性选择器
- 伪类选择器
- 伪元素选择器
css样式的优先级
!important > 内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器 > 通配符选择器 > 继承 > 浏览器默认属性
常见的自适应布局
- flex布局
- 圣杯布局
- 双飞翼布局
圣杯/双飞翼都是用float实现的
css常用的布局方式有哪些
- 流式布局 最基本的布局,就是顺着html像流水一样流下来
- 绝对定位 利用position: absolute进行绝对定位的布局
- float布局 最初用来解决多栏布局的问题。比如圣杯/双飞翼的布局都可以用float来实现
- 珊格布局 bootstrap用的布局,把页面分为24份,通过row和col进行布局
- flex布局 css3的布局可以非常灵活地进行布局和排版
- grid布局 网格布局
style标签写在body前和body后的区别是什么
放在body前会跟HTML同时渲染
放在body后,浏览器会先渲染HTML,再渲染CSS
则会导致一开始出现一个没有样式的界面,再跳到有样式的界面
简述下你理解的优雅降级和渐进增强
- 优雅降级 先做好一个完善的具备完整体验的版本,再向下做兼容
- 渐进增强 先做好一个可以基本正常使用的版本,再慢慢丰富体验和内容
css可以继承的属性
- color
- font-size
- font-weight
- font-style
- font-family
js小分队🐎
var/let/const的区别
let和const是es6新增的
- 变量提升
- 作用域
- 初始值
- 重复声明
变量提升方面 只有var可以
作用域方面 var是函数作用域 let和const是块级作用域 不支持外部访问
初始值和重复声明方面 const在定义变量的时候需要赋值 且不可以重复声明 但是可以对数组元素或者对象属性进行修改 这个不算是重复声明
然后const不能用来定义for循环的迭代变量 因为它违反了const不能重复声明这条限制
JavaScript的参数是以什么方式进行传递的
- 基本类型: 值传递
- 复杂类型: 地址传递
说一下js的继承
- 原型继承
- 组合继承
- 寄生组合继承
原型继承
通过改造原型链 继承父级构造函数原型上的方法
Student.prototype = new Person()
组合继承
指的是将原型链和call()方法进行结合
通过原型链实现对原型上的属性和方法进行继承
然后通过call()方法对实例的属性进行继承
这样原型上定义的方法得到了函数复用 又能保证每个实例都有自己的属性
寄生组合继承
student实例上有的属性 原型__proto__上不再需要这些属性
这时候可以通过Object.create(参数对象)进行改装
它会创建一个新的对象 它的__proto__会指向传入的参数对象
说一下es6新增的特性
- let和const
- 箭头函数
- 解构赋值
- 展开运算符
- Promise
- Class类
- Symbol数据类型
async/await是什么
async test() {
let res = await login()
}
async/await是es8新增的 是基于promise的
async用于声明一个异步函数 它会将一个常规函数转为promise 返回值也是一个promise对象
await用于等待异步的功能执行完毕 会强制async函数中的代码等待 直到promise完成并返回结果
相对于promise async/await的优势
- 同步化代码的阅读体验 虽然promise摆脱了回调地狱 但是.then链式调用的阅读负担还是有的
- 和同步代码更一致的错误处理方式 async/await可以使用更加成熟的try/catch做处理 比promise的错误捕获更加简洁直观
说一下promise
const promise = new Promise((resolve, reject) => {
// some code
})
通过Promise构造函数创建一个promise实例
这个实例接受一个函数作为参数 其中这个函数的两个参数分别为resolve和reject函数
resolve函数是将promise的状态从未完成变为成功
reject函数是将promise的状态从未完成变为失败
然后我们可以通过.then方法分别指向resolved和rejected的回调函数
promise.then((value) => {
// success
}, (err) => {
// fail
})
Promise对象有两个特点
- 对象的状态不受外界影响
- 一旦状态改变就不会再变 任何时候都可以得到这个结果
Promise也有一些缺点
- 无法取消promise 一旦新建就会立刻执行 无法中途取消
- 如果不设置回调 内部抛出的错误 不会反应到外部
说一下promise的all方法
Promise.all()方法用于将多个promise实例包装成一个新的promise实例
// 接受一个数组作为参数
const p = Promise.all([p1, p2, p3])
p的状态由数组中的promise实例决定的 分为两种情况
resolved情况
只有数组中的promise实例都为resolved p的状态才是resolved
此时数组中的promise实例的返回值会组成一个数组 传递给p的回调函数
rejected情况
如果数组中的promise实例有一个被rejected了 p的状态就为rejected
此时第一个被rejected的实例的返回值 会传递给p的回调函数
浏览器存储方式的应用场景
- cookie用于保存一些很小的数据 比如会话标识
- sessionStorage用于存储一个会话中的数据
- localStorage用于持久化数据
浏览器存储方式的区别
- 是否发送给服务器
- 存储大小限制
- 数据有效期
- 作用域
是否发送给服务器
cookie数据始终在同源的http请求中携带(即使不需要) 也就是cookie在浏览器和服务器间来回传递
sessionStorage和localStorage不会自动把数据发送给服务器 仅在本地保存
存储大小限制
cookie数据不能超过4K
sessionStorage和localStorage可以达到5M
数据有效期
cookie只在设置的过期时间之前有效 即使窗口或者浏览器关闭
sessionStorage只在当前浏览器窗口关闭之前有效
localStorage始终有效 即使窗口或浏览器关闭也一直保存 可以做数据持久化
作用域
localstorage和cookie在所有同源窗口中都是共享的
sessionStorage不在不同的窗口中共享 即使是同一个页面
jq中each和js中forEach的区别
遍历数组
let arr = [1, 8, 5, 6, 9]
let res = arr.forEach((ele, index) => {
console.log(ele, index);
})
console.log(res); /* undefined 没有返回值 */
let arr = [1, 8, 5, 6, 9]
let res = $.each(arr, (index, ele) => {
console.log(index, ele);
})
console.log(res); /* 被遍历的数组 */
原生回调参数是(ele, index) jq是(index, ele)
原生没有返回值 jq是被遍历的数组
遍历对象
let obj = {name: 'demo', age: 18, gender: 'male'}
for(let key in obj) {
console.log(key, obj[key]);
}
let obj = {name: 'demo', age: 18, gender: 'male'}
$.each(obj, (key, value) => {
console.log(key, value);
})
原生通过for-in遍历对象 jq可以通过each遍历对象
说一下你对this的理解
this是一个在运行时才会进行绑定的引用,在不同的情况下它可能会被绑定不同的对象
- 函数调用模式fn()
- 方法调用模式obj.fn()
- 上下文调用模式(想让this指向谁就指向谁)
- 构造函数模式(使用new创建对象时也会进行this绑定)
- 箭头函数的this(不能被new给调用)
函数调用模式fn()
默认被绑定到全局对象中 也就是window
如果是nodejs就是global对象
如果开启严格模式的话 this为undefined
方法调用模式obj.fn()
谁调用绑定谁 比如如果函数是对象发起的 这个this就绑定为对象
上下文调用模式
想让this指向谁就指向谁
有三种方式
- call
- apply
- bind
call()方法第一个参数为this 其他为实参列表
非严格模式下 指定null/undefined的话 会替换为全局对象window 原始值会被包装
apply()方法和call()方法差不多 它们的区别是
call()方法接受的是一个参数列表 而apply()方法接受的是一个包含多个参数的数组(也可以是伪数组)
bind()方法不调用函数 它会创建并返回一个新的函数
新的函数和借用的函数是一模一样的
但是新函数内的this已经被改变成了bind的参数
构造函数模式
当使用new调用构造函数的时候 会创建一个新的对象
这个新的对象的this就指向构造函数
箭头函数的this
箭头函数不同于传统函数,它没有自己的this
它的this是捕获外层上下文的this值作为自己的this值
并且由于箭头函数没有属于自己的this,它是不能被new调用的
用递归实现数组长度为5且元素的随机数在2-32间不重复的值
let arr = new Array(5);
let num = randomNumber();
let i = 0;
// 执行函数
randomArr(arr, num);
// 获取随机数组
function randomArr(arr, num) {
// 如果这个元素不存在 就存进数组中
if (arr.indexOf(num) < 0) {
arr[i] = num;
i++;
} else {
// 再次获取随机数
num = randomNumber();
}
// 如果达到数组的长度了 就输出数组 return掉
if (i >= arr.length) {
console.log(arr);
return;
} else {
// 再次获取随机数组
randomArr(arr, num);
}
}
// 获取随机数
function randomNumber() {
// +2 从2开始找元素 向下取整避免跑出32
return Math.floor(Math.random() * 31 + 2);
}
如何去掉字符串中的空格
let str = ' 1 2 345 6 ';
console.log(str.split(' ').join('')); /* 123456 */
删除字符串中最后一个字符
function trim(str) {
if (typeof str === 'string') {
let trimStr = str.substring(0, str.length - 1);
return trimStr;
}
}
console.log(trim('essdgdg')); /* essdgd */
写一个方法把下划线命名转成驼峰命名
function toCamelCase(str) {
if (typeof str !== 'string') {
return str;
}
const demo = str
.split('_')
.map((item) => item[0].toUpperCase() + item.substr(1))
.join('');
return demo[0].toLowerCase() + demo.substr(1);
}
let testStr = 'if_you_are_my_world';
console.log(toCamelCase(testStr)); /* ifYouAreMyWorld */
JavaScript的回收机制
JS中内存的分配和回收都是自动完成的 在内存不使用的时候会被垃圾回收器自动回收
被分配的内存 一般有如下的生命周期
- 内存分配: 当我们声明变量/函数/对象的时候 系统会自动分配内存
- 内存使用: 即读写内存 也就是使用变量/函数等等
- 内存回收: 系统会自动回收不再使用的内存 全局变量一般不会被回收
造成内存泄漏的操作有哪些
- 闭包
- 递归
- 死循环
- 没有使用的全局变量
- setInterval(定时器)没有被清除
子函数在作用域内会不会被释放
不会 这是闭包的缺点 会造成内存泄漏
window对象和document对象有什么区别
window对象
代表浏览器中的一个打开的窗口或者框架
window对象会在每次出现的时候被自动创建
所有的表达式都在当前的环境中计算,要引用当前窗口不需要特殊的语法,可以把当前窗口属性作为全局变量使用,例如
可以只写document,而不必写window.document
同样可以把窗口对象的方法当做函数来使用,如:只写alert(),而不必写window.alert
window对象实现了核心JavaScript所定义的全局属性和方法
document对象
代表整个HTML文档,可以用来访问页面中的所有元素
每一个载入浏览器的HTML文档都会解析为document对象
document对象可以让我们通过js对HTML页面中的所有元素进行访问
document对象是window对象的一部分 可以通过window.document属性对其进行访问
写一个获取数组的最大最小值的方法
let demo = [25, 62, 91, 78, 34, 62];
let max_res = Math.max.apply(this, demo);
let min_res = Math.min.apply(this, demo);
console.log(max_res); /* 91 */
console.log(min_res); /* 25 */
// 用this和Array都是全等的
console.log(Math.min.apply(this, demo) === Math.min.apply(Array, demo)); /* true */
如何删除对象的a属性
let obj = { a: 0, b: 1 };
delete obj.a;
console.log(obj);
说一下常用的数组方法
- push(更新数组)
- map(返回每个元素调用函数后的返回值)
- sort(排序数组)
- splice(指定索引添加/删除元素)
- forEach(遍历数组)
- shift(删除第一个元素)
- pop(删除最后一个元素)
- includes(判断是否存在指定值)
- indexOf(返回指定值的第一个索引 不存在返回-1)
typeof的返回值
一共七种
- number
- string
- boolean
- undefined
- symbol
- object
- function
一些细节
- null也会返回object(因为null是空对象指针)
- array需要通过toString.call()判断 typeof不能判断数组
- symbol为符号
说一下null和undefined的区别
null只有一个值 就是null 它表示空值
null的典型用法
- 作为函数的参数,表示该函数的参数不是对象
- 作为对象原型链的终点
undefined也是只有一个值 就是undefined 它表示已经定义但未赋值
undefined的典型用法
- 调用函数时,应该提供的参数没有提供,该参数等于undefined
- 变量被声明了,但没有赋值时,就等于undefined
- 对象没有赋值的属性,该属性的值为undefined
- 函数没有返回值时,默认返回undefined
说一下js的数据类型
六种基本类型 三种引用类型 其中symbol属于es6的
基本类型
- number
- string
- boolean
- undefined
- null
- symbol
引用类型
- object
- function
- array
浅拷贝和深拷贝的区别及如何实现深拷贝
区别在于是否开辟了一个新的地址
公用一个地址就是浅拷贝 否则就是深拷贝
可以通过浅拷贝+递归实现深拷贝
浅拷贝时判断属性值是否是对象
如果是对象就进行递归操作
这样就实现了深拷贝
请用一行代码实现深拷贝
let demo = JSON.parse(JSON.stringify(obj))
适合用事件委托的事件有哪些
- click
- mousedown
- mouseup
- keydown
- keyup
- keypress
什么是事件委托
事件委托是利用事件的冒泡原理来实现的
优点
- 减少事件注册,节省内存
- 简化了dom节点更新时,相应事件的更新
缺点
- 事件委托基于冒泡,对于不冒泡的事件不支持
- 层级过多,冒泡过程中,可能会被某层阻止掉
什么是冒泡事件
在一个按钮中绑定一个click事件 它会依次在父元素中被触发
如何取消默认事件
通过e.preventDefault()
如何防止冒泡和捕获事件
通过e.stopPropagation()
说一下js有哪些内置对象
- 数值对象Number
- 字符串对象String
- 布尔对象Boolean
- 时间对象Date
- 数学对象Math
- 错误对象Error
- 数组对象Array
- 基础对象Object
- 函数对象Function
- 函数参数集合arguments
说一下原型对象
每个函数都有一个prototype属性 指向它的原型对象
基于构造函数创建的实例 可以访问它的原型对象上的方法
比如toString是Object原型对象上的方法 可以被任何实例作为自己的方法进行调用
实例在调用属性和方法的时候 优先调用自己的
如果自己没有就找原型的 原型没有就找原型的原型 直到原型顶端Object.protototype(原型链的顶端)
hasOwnProperty就是用来判断某个属性是不是自己的
说一下原型链
每个对象都有一个__proto__属性 指向它的构造函数的原型
对象可以通过__proto__属性和构造函数的原型对象进行连接
然后构造函数的原型对象也有__proto__属性
这样就形成了链式结构 我们称之为原型链
说一下作用域链
函数内部可以访问到外部变量
外部函数可以访问到全局变量
这样形成的变量作用域链式结构 被称为作用域链
toString和valueOf的区别
对象的两个方法 Object.protototype
toString() 返回对象的字符串表示 valueOf() 返回对象的字符串/数值或布尔值表示(其实就是返回自身)
toString()在对象的时候 就变成”[object Object]”
表示数组的时候 就变成数组内容以逗号连接的字符串 相当于Array.join(‘,’)
let arr = [1, 2, 3].toString();
let obj = { a: 1 }.toString();
console.log(arr); /* 1,2,3 */
console.log(obj); /* [object Object] */
valueOf的优先级比toString高
Object是在堆还是栈里面
堆里面
栈内存主要用于存储各种基本类型的变量 比如Boolean/Number/String/Undefined/Null
堆内存主要负责像对象Object这种变量类型的存储
垃圾回收的两种方法
垃圾回收
核心思想就是如何判断内存是否已经不再会被使用了
如果是 就视为垃圾释放掉
- 引用计数法
- 标记清除法
引用计数法
定义“内存不再使用”的标准很简单
就是看一个对象是否有指向它的引用
如果没有任何变量指向它了 说明该对象已经不再需要了
标记清除法
标记清除算法将“不再使用的对象”定义为“无法达到的对象”
说一下闭包
闭包是一个函数嵌套另一个函数
优点可以防止全局变量污染
缺点会造成内存泄漏
闭包最大的作用就是用来变量私有
vue小分队🍖
组件通信的几种方式
props/$emit
$emit/$on
vuex
$parent/$children/$refs
provide/inject
如何理解vue的响应式系统
任何component都会有一个与之对应的watcher实例
vue的data上的属性都会被添加getter/setter属性
当vue component render函数被执行的时候 data会被touch(即读写) getter方法就会被调用 此时vue会去记录此vue component所依赖的所有data(这个过程被称为依赖收集)
当data被改动(主要是用户操作) setter方法就会被调用 vue会去通知所有依赖此data的组件调用他们的render函数进行更新
keep-alive的作用
keep-alive主要用于缓存不活动的组件实例 保留组件的状态 避免被重新渲染导致的性能问题
当组件在keep-alive内被切换 组件的activated/deactivated这两个生命周期会被执行
组件一旦被缓存 再次渲染就不会执行created/mounted生命周期
要求同时只有一个子组件被渲染
vue组件中的data为什么一定是函数
如果data是一个函数的话 每次复用一次组件 就会返回一份新的data
类似于给每个组件实例创建一个私有的数据空间 让各个组件实例都去维护自己的数据
如果单纯的写成对象的形式 会导致所有组件共享一份data 会造成一个变全部变的结果
所以就规定了vue的data必须是函数
这是js的特性 跟vue本身设计无关
js本身也是基于原型链和构造函数的 原型链上一般都是添加函数方法而非对象
说一下vue的生命周期
常规8个 其实11个
- beforeCreate(创建前)/created(创建后)
- beforeMount(挂载前)/mounted(挂载后)
- beforeUpdate(更新前)/updated(更新后)
- beforeDestroy(销毁前)/destroyed(销毁后)
- activated(激活时)/deactivated(停用时)
- errorCaptured(错误调用时)
然后我们常用的生命周期是created/mounted
如果是请求一些数据 可以把方法放在created中
如果要操作dom的话 需要把方法放在mounted中
异步请求可以在created/mounted 具体看是否需要操作dom
第一次页面加载的时候会触发beforeCreate/created/beforeMount/mounted这几个生命周期
说一下自定义指令
自定义指令分为局部和全局 一般全局使用
// 全局自定义指令
Vue.directive('color', {
bind(el,binding) {
el.style.color = binding.value
},
update(el,binding) {
el.style.color = binding.value
}
})
其中bind是在第一次绑定到元素时调用 update是在每次更新DOM的时候调用
如果两者的逻辑都一样 可以通过function函数简写
// 简写形式
Vue.directive('color', function(el, binding) {
el.style.color = binding.value
})
局部指令写法
directives: {
// 简写形式
color(el, binding) {
el.style.color = binding.value
}
// 常规写法
color: {
bind(el, binding) {
el.style.color = binding.value
},
update(el, binding) {
el.style.color = binding.value
}
}
}
什么是MVVM
- model层(模型层)
- view层(视图层)
- viewmodel层(视图模型层)
model层 通过ajax/fetch完成客户端和服务端的业务模型同步
view层 作为视图模板的存在
viewmodel层 负责暴露数据给view层
对view层的(指令声明/事件绑定声明/数据绑定声明)进行实际业务的实现
viewmodel的底层会监听object.defineproperty 当数据变化的时候 view层自动更新
vue的实例就是属于viewmodel
vue中的key有什么作用
key的主要作用是为了高效更新虚拟DOM
通过这个key 我们的diff操作可以更加准确快速
vue同时使用v-for和v-if哪个先调用
当v-if和v-for一起使用的时候 v-for的优先级会更高
官方是不建议同时使用的 如果同时使用的话 每次v-for都会执行v-if 会造成不必要的计算 浪费性能
可以通过computed计算属性过滤掉不需要显示的item
computed: {
items() {
return this.list.filter(item => item.isShow)
}
}
或者通过外层嵌套template 在这层中进行v-if判断 然后再进行内部v-for循环
<template v-if="isShow">
<p v-for="item in list">
</template>
v-show和v-if的区别
两者都是用来显示隐藏元素的
不同的是
- v-if是通过销毁和重建DOM让元素显示隐藏的
- v-show是通过修改display属性让元素显示隐藏的
- v-if有更高的切换开销 而v-show是有更高的首次渲染开销
- v-if有配套的v-if-else和v-else v-show没有
为什么要替换defineProperty
因为defineProperty无法对数组对象进行深度监听
defineProperty只能响应首次渲染时候的属性 后面添加的属性是不会渲染的
proxy可以直接监听数组/对象的变化而非属性
proxy返回的是一个新的对象 我们可以直接操作新的对象就可以达到目的 而defineProperty只能遍历对象的属性进行修改
而且proxy的配置项有13种 可以做更细致的事情 这是defineProperty做不到的
defineProperty的优势就是兼容性好 可以支持ie9
vue3和vue2的区别
- setup代替了之前的beforeCreate和created
- proxy代替Object.defineProperty(vue2的双向绑定的核心)
- diff算法的提升
- typeScript的支持
- 打包体积的优化
- 使用自己的构建工具vite
虚拟dom的优劣
- 保证性能下限
- 无需手动操作DOM
- 跨平台
- 无法进行极致优化
保证性能下限 虚拟DOM可以经过diff找出最小差异,然后批量进行patch,这种操作虽然比不上手动优化,但是比起粗暴的DOM操作性能要好很多,因此虚拟DOM可以保证性能下限
无需手动操作DOM 虚拟DOM的diff和patch都是在一次更新中自动进行的,我们无需手动操作DOM,极大提高开发效率
跨平台 虚拟DOM本质上是JavaScript对象,而DOM与平台强相关,相比之下虚拟DOM可以进行更方便地跨平台操作,例如服务器渲染、移动端开发等等
无法进行极致优化 在一些性能要求极高的应用中虚拟DOM无法进行针对性的极致优化
虚拟dom的实现原理
- 虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象
- 状态变更时,记录新树和旧树的差异
- 最后把差异更新到真正的dom中
说一下双向绑定的原理
利用Object.defineProperty劫持对象的访问器
当属性值发生变化的时候 我们获取变化进行后续的操作
vue3通过proxy进行类似的操作
小程序小分队👻
说一下小程序的支付流程
- 获取用户的token
- 创建订单
- 获取预支付参数
- 发起微信支付
用户的token需要五个参数
其中code通过wx.login()获取 其他通过wx.getUserProfile()获取
然后就可以创建订单了 订单需要请求头和请求体参数
- 请求头就需要token
- 请求体是用户的商品列表/总价/收获地址等信息
这时候就可以通过订单编号获取预支付参数(五个参数)
然后通过wx.requestPayment()传入预支付参数 就可以发起微信支付了
小程序怎么实现双向绑定
通过bindinput属性
小程序是单向数据流 我们可以通过bindinput属性来实现双向数据绑定
- 在bindinput属性中定义个方法
- 在data中定义个message
- 通过e.detail.value获取用户输入的值
- 然后通过this.setData()更新数据
<text>这是用来显示文本的:</text>
<input bindinput="getMessage" placeholder="这是输入框" />
data: {
message: ''
},
getMessage(e) {
let message = e.detail.value
this.setData({
message
})
},
bindtap和catchtap的区别
bindtap不能阻止事件冒泡 catchtap可以
小程序的本地存储和web的区别
- 使用的方法不同
- 有没有类型转换
小程序是通过这两个方法进行缓存获取数据的
- wx.setStorageSync()
- wx.getStorageSync()
web是通过这两个方法
- localStorage.setItem()
- localStorage.getItem()
web不管存入什么类型的数据都会通过toString()变成字符串存储
小程序不存在什么类型转换 存放什么类型的数据 获取的时候就是什么类型的数据
setData和data的区别
this.setData()用于将数据从逻辑层发送到视图层 同时改变对应的this.data的值
用this.data会造成页面内容不更新的问题
open-type的作用
用于获取手机号/客服/用户信息等等
如何获取用户信息
通过wx.getUserProfile()获取
原来是通过open-type=”getUserInfo” 然后数据在e.detail里面
如何授权登录
通过code和第三方服务器进行注册登录
code通过wx.login()获取
用户信息通过wx.getUserProfile()获取 数据在res里面
然后通过wx.setStorageSync()缓存用户信息
wx.getStorageSync()进行获取用户信息
this.setData()更新数据
http协议小分队🌐
协议和域名不同的情况是否同域
不同的
同域的要求是 请求地址中的协议/域名及端口号都相同
只要有一个不相同就是跨域
浏览器输入url后发生了什么
- DNS解析
- TCP连接
- 发送HTTP请求
- 服务器处理请求 并返回HTTP报文
- 浏览器解析渲染页面
- 连接结束
说一下跨域及如何解决跨域
跨域是浏览器的同源策略造成的
有三种解决方案
- cors
- jsonp
- proxy
常用的是cors 只需要后端四行代码就可以搞定
jsonp的话 只是加它并不能处理跨域问题 因为它不能post请求
proxy是在项目开发的时候使用 上线了一般接口和项目都是在同一个服务器 也就不存在跨域问题了
说一下你知道的状态码
状态码主要分为5种类型
- 1xx 消息
- 2xx 成功
- 3xx 重定向
- 4xx 客户端错误
- 5xx 服务器错误
常见的状态码
- 200 成功
- 301 永久重定向
- 302 临时重定向
- 304 使用缓存不改变
- 400 错误请求
- 401 请求授权失败
- 403 禁止访问
- 404 找不到资源
- 500 服务器内部错误
- 504 网关超时
其他小分队🔫
常用的五个网站
- GitHub
- MDN中文网
- 掘金
- 相关技术官网文档
- 哔哩哔哩