kangkai124 / blog

开发笔记
https://kangkai124.github.io/blog/
MIT License
26 stars 4 forks source link

每天一道题,早日BAT #12

Open kangkai124 opened 5 years ago

kangkai124 commented 5 years ago

Thanks to Daily-Interview-Question,and all participants.

kangkai124 commented 5 years ago

1. ['1', '2', '3'].map(parseInt) what&why

from https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/4

map

map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

var new_array = arr.map(function callback(currentValue[,index[, array]]) {
 // Return element for new_array
 }[, thisArg])

callback 回调函数需要三个参数:

parseInt

parseInt() 函数解析一个字符串参数,并返回一个指定基数的整数 (数学系统的基础)。

const intValue = parseInt(string[, radix]);
string 必需。要被解析的字符串。
radix 可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。

回到题目,对于每个迭代的 mapparseInt() 接收两个参数:字符串和基数。 即返回的值是:

parseInt('1', 0)  // 1,把 “1” 转换为十进制
parseInt('2', 1)  // NaN,,因为基数小于 2
parseInt('3', 2)  // NaN,3 超过二进制的位数的值

因此最后的答案是:

[1, NaN, NaN]

['1', '2', '3'].map(parseInt) 这段代码本来的目的是转换每个字符串为数字,但是由于 parseInt 参数不定的原因产生了意外的问题,所以使用 unary (一元函数) 来改写这个例子:

const unary = fn => val => fn(val)
['1', '2', '3'].map(unary(parseInt))  
// [1, 2, 3]

unary 函数在执行的过程中,会创建并且返回一个函数,该返回函数最多接受一个参数,然后忽略其余的任何参数。在只给出第一个参数的情况下,调用函数 unary 所提供的函数 fn。

kangkai124 commented 5 years ago

2. 以下代码执行结果

var obj = {
    '2': 3,
    '3': 4,
    'length': 2,
    'splice': Array.prototype.splice,
    'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)

from https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/76

涉及知识点

ArrayLike 类数组

对象,有 length 属性

Array.push

push方法将值追加到数组中。

push 方法有意具有通用性。该方法和 call() 或 apply() 一起使用时,可应用在类似数组的对象上。push 方法根据 length 属性来决定从哪里开始插入给定的值。如果 length 不能被转成一个数值,则插入的元素索引为 0,包括 length 不存在时。当 length 不存在时,将会创建它。

简而言之,push 的作用就是: 1:push 方法根据 length 属性来决定从哪里开始插入给定的值。 2:push 是特意设计为通用的,Array.prototype.push 可以在一个对象上工作。

对象转数组的方法

Array.from(), splice(), concat() 等

回到题目

这个 obj 对象就是类数组,调用 push 方法的时候,将 this.length 作为 push 的插入位。该 length 为2, 所以从数组的 2+1 位,也就是下标为 2 的地方开始插入,相当于:

objArr[2] = 3
objArr[3] = 4

push 两项之后,其实此时 length 已经变为 2+2 = 4, 但是数组前两项由于没有内容,所以为空(Empty),打印出来就是 [ , , 1, 2] length为4

其他

image 我在 chrome devtools 控制台打印如上,发现对象添加了splice属性后并没有调用就会变成类数组对象,原来是控制台中 DevTools 猜测类数组的一个方式,代码 在这里

kangkai124 commented 5 years ago

3. 双向绑定 v-model 和 vuex 是否冲突

from https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/81

在严格模式下直接使用会有问题。

一个好的解决方案:

<input v-model="message">
// ...
computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  },
methods: {
  mutations: {
    updateMessage (state, message) {
      state.obj.message = message
    }
  }
}

相关连接:computed 绑定过程

kangkai124 commented 5 years ago

4. call 和 apply 的区别,哪个性能更好一些

  1. call 和 apply 的作用是一样的,区别在于接收的参数
  2. call 和 apply 接收的第一个参数都是内部 this 的指向
  3. call 接收的从第二个参数开始是不固定的,有多少都会作为函数的参数,而 apply 接收的第二个参数是数组或类数组。
  4. call 性能更好,尤其是 es6 引入了 Spread operator,可以处理参数是数组的情况
let params = [1,2,3,4]
xx.call(obj, ...params)
kangkai124 commented 5 years ago

5. 为什么前端监控要用 1*1 像素的透明 gif 图片打点

from https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/87

所谓的前端监控,就是由Web页面将用户信息(UA/鼠标点击位置/页面报错/停留时长/etc)上报给服务器的过程。

使用 1*1 像素的透明 gif 图片打点的原因总结如下:

  1. img 没有跨域问题,排除 Ajax
  2. 不会阻塞页面加载,只需要在 js 里 new 一个 Image 对象即可,排除 JS/CSS 资源上报
  3. 需要透明,不影响页面UI,且对比 bmp、png,gif 体积最小
<script type="text/javascript">
 var thisPage = location.href;
 var referringPage = (document.referrer) ? document.referrer : "none";
 var beacon = new Image();
 beacon.src = "http://www.example.com/logger/beacon.gif?page=" + encodeURI(thisPage)
 + "&ref=" + encodeURI(referringPage);
</script>

reference:为什么前端监控要用GIF打点

kangkai124 commented 5 years ago

6. 实现 (5).add(3).minus(2) 功能

from https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/88

其实思路很简单,就是给 Number 原型上加方法

Number.prototype.add = function(n) {
  return this.valueOf() + n;
};
Number.prototype.minus = function(n) {
  return this.valueOf() - n;
};

但是根据一位同学的答案引出了 JS 的经典的浮点数陷阱问题,进而对此进行了一番深入学习。 学习内容笔记 在这里

kangkai124 commented 5 years ago

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

from https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/90

  1. Object.defineProperty 本身可以监控到数组下标变化,但是从性能/体验的性价比考虑,放弃了这个特性,所以 vue 不支持通过下标操作数组来实时响应。
  2. Object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy可以劫持整个对象,并返回一个新的对象。
  3. Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

第一点,详细可以参考:记一次思否问答的问题思考:Vue为什么不能检测数组变动

PS:for 循环 和 forEach 对比:

10000 长度数组,只有两个下标有值

2

然后再对比长度相同,但是一个只有两个下标有值,一个是所有下标都有值:

var arr = [1]; arr[100000] = 1
function a(){
    console.time()
    arr.forEach(item=>{})
    console.timeEnd()
}
a(); //default: 2.5810546875ms
a(); //default: 2.02099609375ms

var arr1 = new Array(100000).fill(1);
function b(){
    console.time()
    arr1.forEach(item=>{})
    console.timeEnd()
}
b(); //default: 2.355224609375ms
b(); //default: 1.94189453125ms

两个要花费同样的时间来遍历,所以可以确定遍历性能跟下标有没有值没太大关系。

kangkai124 commented 5 years ago

8. {1:222, 2:123, 5:888} 转为 [222, 123, null, null, 888, null, null, null, null, null, null, null]

from https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/96

let obj = { 1:222, 2:123, 5:888 }
const result = Array.from({ length: 12 }, (_, ind) => obj[ind + 1] || null)
// const result = Array.from({ length: 12 }).map((_, ind) => obj[ind + 1] || null)
const obj = { 1: 222, 2: 123, 5: 888 };
const result = Array.from({ ...obj, length: 13 }, item => item || null).slice(1);
console.log(result);
let obj = {1:222, 2:123, 5:888};
obj.length = 13;
obj[Symbol.iterator] = Array.prototype[Symbol.iterator];
// obj ==> {1: 222, 2: 123, 5: 888, length: 12, Symbol(Symbol.iterator): ƒ}
let _obj = [...obj].slice(1);
// _obj ==> [222, 123, undefined, undefined, 888, undefined, undefined, undefined, undefined, undefined, undefined, undefined]
let newObj =  _obj.map((item) => {if(item === undefined) {return null;} else {return item;}});
console.log(newObj);

为什么 Symbol.iterator 赋值后,obj 可以使用 slice 方法???

kangkai124 commented 5 years ago

9. 要求设计 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

链式调用,所以每个方法需要返回 this。sleep 和 sleepFirst 的区别在于队列中的顺序。

class LazyManClass {
  constructor(name) {
    this.name = name
    this.queue = []
    console.log(`Hi I am ${this.name}`);
    // 由于 setTimeout 是异步的,当链式调用结束后,queue 队列成形,
    // 此时调用 this.next() 开始按照 queue 队列执行方法
    setTimeout(() => {
      this.next()
    }, 0)
  }

  eat(food) {
    const fn = () => {
      console.log(`I am eating ${food}`)
      this.next()
    }
    this.queue.push(fn)
    return this
  }

  sleep(time) {
     const fn = () => {
      setTimeout(() => {
        console.log(`等待了${time}秒...`)
        this.next()
      }, time * 1000)
    }
    this.queue.push(fn)
    return this
  }

  sleepFirst(time) {
     const fn = () => {
      setTimeout(() => {
        console.log(`等待了${time}秒...`)
        this.next()
      }, time * 1000)
    }
    this.queue.unshift(fn)
    return this
  }

  next() {
    const fn = this.queue.shift()
    fn && fn()
  }
}

function LazyMan(name) {
  return new LazyManClass(name)
}
class LazyManClass {
  constructor(props) {
    this.sub = []
    console.log(`Hi I am ${props}`)
    setTimeout(() => {
      this.start()
    }, 0)
  }

  eat(params) {
    this.sub.push(function () {
      console.log(`I am eating ${params}`)
    })
    return this
  }

  sleepFirst(s) {
    this.sub.unshift(this.delay(s))
    // 这边还没有返回  同步就继续执行了
    return this
  }

  delay(s) {
    return () => {
      return new Promise(resolve => {
        setTimeout(function () {
          console.log(`等待了${s}秒...`)
          resolve()
        }, s * 1000)
      })
    }
  }
  sleep(s) {
    this.sub.push(this.delay(s))
    // 这边还没有返回  同步就继续执行了
    return this
  }
  async start() {
    for (const iterator of this.sub) {
      await iterator()
    }
  }
}
function LazyMan(props) {
  return new LazyManClass(props)
}