《前端100问》60

Vue 的响应式原理中 Object.defineProperty 有什么缺陷?

为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

  1. Object.defineProperty 无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应;
  2. Object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。
  3. Proxy 可以劫持整个对象,并返回一个新的对象。
  4. Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

怎么让一个 div 水平垂直居中

<div class="parent">
  <div class="child"></div>
</div>
  1. flex 布局
div.parent {
    display: flex;
    justify-content: center;
    align-items: center;
}
  1. 绝对定位
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;
}
  1. 使用行内块元素,垂直中线对齐
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}
  1. 执行 var a = {n: 1}; 在堆内存中开辟 a 对象的地址空间
  2. 执行 var b = a; 将 a 指向的地址空间赋值给 b,此时 a 与 b 执行同一块地址空间
  3. 当执行到 a.x = a = {n: 2}; 时,. 运算符优先级高于 = 赋值,先为先前生成的对象添加属性{n:1 x:underfined} ,并赋值 a={n:2}
  4. 然后再执行 = 右边的内容 a = {n: 2}; 此时,重新开辟了一块堆内存地址空间存储 {n:2} ,a 重新指向新的地址空间,旧的地址空间因为有 b 的引用所以不会消失。
  5. 此时进行打印 console.log(a.x) a 指向的是新地址空间
  6. 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 优劣和适用场景。

结构:

  1. display: none 不占空间,不能点击。场景:显示出原来这里不存在的结构
  2. visibility: hidden占据空间,不能点击。场景:显示不会导致页面结构发生变动,不会撑开
  3. opacity: 0 占据空间,可以点击。场景:可以跟transition搭配

继承:

  • opacity:0display:none,是非继承属性。若父节点元素应用了 opacity:0display:none,无论其子孙元素如何挣扎都不会再出现在大众视野;子孙节点消失是由于元素从渲染树消失造成,通过修改子孙节点属性无法显示。

  • visibility:hidden,是继承属性。若父节点元素应用了 visibility:hidden,子孙元素应用 visibility:visible,那么其就会显现出来。

性能:

display: none : 修改元素会造成文档回流,读屏器不会读取display: none元素内容,性能消耗较大

visibility: hidden: 修改元素只会造成本元素的重绘,性能消耗较少读屏器读取visibility: hidden元素内容

opacity:0 : 修改元素会造成重绘,性能消耗较少

箭头函数与普通函数(function)的区别是什么?构造函数(function)可以使用 new 生成实例,那么箭头函数可以吗?为什么?

箭头函数是普通函数的简写,可以更优雅的定义一个函数,和普通函数相比,有以下几点差异:

  1. 箭头函数体内的 this 对象,就是定义时所在的作用域中的 this 值,而不是使用时所在的对象。
  2. 箭头函数不可以使用 arguments 对象。该对象在函数体内不存在。如果使用,可以使用 rest 参数代替。
  3. 箭头函数不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
  4. 箭头函数不能使用 new 命令,因为:
    1. 箭头函数没有自己的 this,无法调用 callapplybind
    2. 箭头函数没有 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;”>
  1. 使用 max-width
max-width: 300px;
  1. transform: scale(.625,.625); 等比例缩小
transform: scale(.625,.625);
/* transform-origin: left top; */
/* 默认以图片中心作为起点缩放,位置会与原先位置不同,更改发生起点为左上*/
  1. 盒子自动内减,添加padding
box-sizing: border-box;
padding: 0 180px 0 0;
/* 保持位置不变的,只加右边的 padding 就可以 */

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!