《前端100问》60
Vue 的响应式原理中 Object.defineProperty 有什么缺陷?
为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?
Object.defineProperty
无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应;Object.defineProperty
只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy
可以劫持整个对象,并返回一个新的对象。Proxy
不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。
怎么让一个 div 水平垂直居中
<div class="parent">
<div class="child"></div>
</div>
- flex 布局
div.parent {
display: flex;
justify-content: center;
align-items: center;
}
- 绝对定位
div.parent {
position: relative;
}
div.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* 或者 */
div.child {
width: 50px;
height: 10px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -25px;
margin-top: -5px;
}
/* 或 */
div.child {
width: 50px;
height: 10px;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
}
- 使用行内块元素,垂直中线对齐
div.parent {
font-size: 0;
text-align: center;
}
.parent::before {
content: '';
display: inline-block;
width: 0;
height: 100%;
vertical-align: middle;
}
div.child {
display: inline-block;
vertical-align: middle;
}
输出以下代码的执行结果并解释为什么
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
console.log(a.x)//underfined
console.log(b.x)//{n:2}
- 执行
var a = {n: 1};
在堆内存中开辟 a 对象的地址空间 - 执行
var b = a;
将 a 指向的地址空间赋值给 b,此时 a 与 b 执行同一块地址空间 - 当执行到
a.x = a = {n: 2};
时,.
运算符优先级高于=
赋值,先为先前生成的对象添加属性{n:1 x:underfined}
,并赋值a={n:2}
- 然后再执行
=
右边的内容a = {n: 2};
此时,重新开辟了一块堆内存地址空间存储{n:2}
,a 重新指向新的地址空间,旧的地址空间因为有 b 的引用所以不会消失。 - 此时进行打印
console.log(a.x)
a 指向的是新地址空间 console.log(b.x)
b 指向的是旧地址空间
冒泡排序如何实现,时间复杂度是多少, 还可以如何改进?
进行判断是否进行的元素交换,如果没有进行元素交换说明,数组以及排序完成,后面的遍历没有进行的必要,最好的情况即为 O(n)
/**
* 冒牌排序学习:
* 使用es6语法,优化冒泡轮数
* 冒泡思路:
* (X,X,X,X,X,X) 6个元素,两两相比较:第一个跟第二个比较,符合就交换,然后第二个跟第三个比较,依次类推……
* 第一轮冒泡: X X X X X X 6个元素,比较5次得出最大值
* 第二轮冒泡: X X X X X 5个元素,比较4次得出次大值
* 第三轮冒泡: X X X X 4个元素,比较3次得出次大值
* 第四轮冒泡: X X X 3个元素,比较2次得出次大值
* 第五轮冒泡: X X 2个元素,比较1次得出次大值
* 第六轮冒泡: X 1个元素,不用比较了
* 总结规律:n个元素,进行n-1次比较,冒泡一次得出来一个最大值
* 比较过程中:元素两两比较,只需要进行(n-冒泡次数)次比较,就能得出最大元素
*/
const bubbleSort = arr => {
//冒泡轮数循环变量控制
for (let i = 1; i < arr.length; i++) {
//设置变量exchange = flase
//每一轮都将变量重置
//当本轮比较没有发生元素移动,说明数组已经排序完成,后续比较不需要再进行下去
//利用变量退出循环
let exchange = false
//规律:n个元素比较n-1次,得出最大值
//冒泡过程中:元素两两比较,只需要进行(n-轮数)次比较,就能得出最大元素
for (let j = 0; j < arr.length - i; j++) {
//元素两两比较
if (arr[j] > arr[j + 1]) {
//临时变量,交换元素,
let temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
exchange = true
}
}
//判断是否发生了元素交换,如果没有就退出循环
if (!exchange) break
}
return arr
}
某公司 1 到 12 月份的销售额存在一个对象里面
如下:{1:222, 2:123, 5:888},请把数据处理为如下结构:[222, 123, null, null, 888, null, null, null, null, null, null, null]。
let obj = {
1: 222,
2: 123,
5: 888
}
obj.length = 12
let data = Array.from(obj).slice(1)
let newData = data.map(item => {
if (item === undefined) return null
else return item
})
newData.push(null)
console.log(newData)
利用 length
属性将伪数组转换为数组遍历操作。
要求设计 LazyMan 类,实现以下功能。
LazyMan('Tony');
// Hi I am Tony
LazyMan('Tony').sleep(10).eat('lunch');
// Hi I am Tony
// 等待了10秒...
// I am eating lunch
LazyMan('Tony').eat('lunch').sleep(10).eat('dinner');
// Hi I am Tony
// I am eating lunch
// 等待了10秒...
// I am eating diner
LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(10).eat('junk food');
// Hi I am Tony
// 等待了5秒...
// I am eating lunch
// I am eating dinner
// 等待了10秒...
// I am eating junk food
回答:
class LazyManClass {
constructor(name) {
// 任务队列
this.taskList = []
this.name = name
setTimeout(() => {
// body
this.next()
}, 0)
console.log(`Hi I am ${this.name}`)
}
eat(food) {
const fn = () => {
console.log(`I am eating ${food}`)
this.next()
}
this.taskList.push(fn)
return this
}
sleepFirst(time) {
const fn = () => {
setTimeout(() => {
console.log(`等待了${time}秒...`)
this.next()
}, 1000 * time)
}
this.taskList.unshift(fn)
return this
}
sleep(time) {
const fn = () => {
setTimeout(() => {
console.log(`等待了${time}秒...`)
this.next()
}, 1000 * time)
}
this.taskList.push(fn)
return this
}
next() {
var fn = this.taskList.shift()
fn && fn()
}
}
function LazyMan(name) {
return new LazyManClass(name)
}
//LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(10).eat('junk food');
思路:
执行 LazyMan('Tony')
方法会 返回一个 new 的 LazyManClass
会执行 constructor
内的代码,同时会创建一个异步宏任务队列执行对象的 next
方法
next
方法会返回任务队列第一个任务且执行,在对象每个方法内都创建一个异步任务执行同时在异步任务内调用next
方法,不同的是其他任务都是 push
到任务队尾,而sleepFirst
是插到队列首,在next
执行时会先执行。
分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景。
结构:
display: none
不占空间,不能点击。场景:显示出原来这里不存在的结构visibility: hidden
占据空间,不能点击。场景:显示不会导致页面结构发生变动,不会撑开opacity: 0
占据空间,可以点击。场景:可以跟transition搭配
继承:
opacity:0
和display:none
,是非继承属性。若父节点元素应用了opacity:0
和display:none
,无论其子孙元素如何挣扎都不会再出现在大众视野;子孙节点消失是由于元素从渲染树消失造成,通过修改子孙节点属性无法显示。visibility:hidden
,是继承属性。若父节点元素应用了visibility:hidden
,子孙元素应用visibility:visible
,那么其就会显现出来。
性能:
display: none
: 修改元素会造成文档回流,读屏器不会读取display: none元素内容,性能消耗较大
visibility: hidden
: 修改元素只会造成本元素的重绘,性能消耗较少读屏器读取visibility: hidden元素内容
opacity:0
: 修改元素会造成重绘,性能消耗较少
箭头函数与普通函数(function)的区别是什么?构造函数(function)可以使用 new 生成实例,那么箭头函数可以吗?为什么?
箭头函数是普通函数的简写,可以更优雅的定义一个函数,和普通函数相比,有以下几点差异:
- 箭头函数体内的
this
对象,就是定义时所在的作用域中的this
值,而不是使用时所在的对象。 - 箭头函数不可以使用
arguments
对象。该对象在函数体内不存在。如果使用,可以使用rest
参数代替。 - 箭头函数不可以使用
yield
命令,因此箭头函数不能用作Generator
函数。 - 箭头函数不能使用
new
命令,因为:- 箭头函数没有自己的
this
,无法调用call
、apply
、bind
- 箭头函数没有
prototype
属性,而new
命令在执行的时候需要将构造函数的prototype
赋值给对象的__proto__
- 箭头函数没有自己的
给定两个数组,写一个方法来计算它们的交集。
例如:给定 nums1 = [1, 2, 2, 1],nums2 = [2, 2],返回 [2, 2]。
const getIntersection = (arr1, arr2) => {
//应该遍历短数组,判断是否包含在长数组内
//应该做一层判断
let s
let l
if (arr1.length > arr2.length) {
l = arr1
s = arr2
} else {
l = arr2
s = arr1
}
const r = s.filter(item => {
// return arr2.indexOf(item) > -1
return l.includes(item)
})
return r
}
哈希表:
const intersect = (nums1, nums2) => {
const map = {}
const res = []
for (let n of nums1) {
if (map[n]) {
map[n]++
} else {
map[n] = 1
}
}
for (let n of nums2) {
if (map[n] > 0) {
res.push(n)
map[n]--
}
}
return res
}
console.log(intersect(nums2, nums1))
思路:
初始化 map
为一个对象,数组1中的每一项都作为 map
的属性,如果存在此属性,则属性值自增1
不存在则初始化为1
遍历数组2,数组2中的每一项都作为map
的属性去读取,读到值 > 0 说明数组2存在相同项直接存入结果数组,并且map
属性值自减1,说明有一项相交,防止重复所以自减1
返回结果数组
已知如下代码,如何修改才能让图片宽度为 300px ?注意下面代码不可修改。
<img src="1.jpg" style="width:480px!important;”>
- 使用
max-width
max-width: 300px;
transform: scale(.625,.625);
等比例缩小
transform: scale(.625,.625);
/* transform-origin: left top; */
/* 默认以图片中心作为起点缩放,位置会与原先位置不同,更改发生起点为左上*/
- 盒子自动内减,添加
padding
box-sizing: border-box;
padding: 0 180px 0 0;
/* 保持位置不变的,只加右边的 padding 就可以 */
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!