LinDaiDai/niubility-coding-js

🍃第1期第3题:实现 arr[-1] = arr[arr.length - 1]

Opened this issue · 0 comments

实现 arr[-1] = arr[arr.length - 1]

这道题的意思是:提供一个createArr()方法,用此方法创建的数组满足arr[-1] = arr[arr.length - 1]

function createArr (...elements) {
  // ...代码
  return arr
}
var arr1 = createArr(1, 2, 3)
console.log(arr1[-1]) // 3
console.log(arr1[-2]) // 2

解题思路:

其实对于这类题目,我首先想到的会是Object.defineProperty()或者Proxy。因为这里涉及到了对数组值的获取,显然用Proxy是比较合适的。什么?你问我为什么不用Object.defineProperty()?因为这个方法是针对于对象的某一个属性的呀,对数组来说不合适。

所以对于这道题,我们也许可以使用Proxy代理每次传入进来的下标,也就是重写一下数组的get方法,在这个方法中我们去处理这方面的逻辑,一起来看看代码吧😊:

function createArr (...elements) {
  let handler = {
    get (target, key, receiver) { // 第三个参数传不传都可以
      let index = Number(key) // 或者 let index = ~~key
      if (index < 0) {
        index = String(target.length + index)
      }
      return Reflect.get(target, index, receiver)
    }
  }
  let target = [...elements] // 创建一个新数组
  return new Proxy(target, handler)
}
var arr1 = createArr(1, 2, 3)
console.log(arr1[-1]) // 3
console.log(arr1[-2]) // 2

注意点:

  • get接收到的第二个参数key表示的是数组下标,它是字符串形式,所以需要转为Number,当然方法有很多种了,使用Number()也可以,使用~~双非按位取反运算符也可以。对比于Number()的好处就是Number(undefined)会转换为NaN;但是使用~~能保证一直是数字,~~undefined === 0。(什么?你还不知道区别?那你得看霖呆呆的这篇文章了:JS中按位取反运算符~及其它运算符)

  • 接下来只需要判断一下传入进来的下标是不是小于0的,小于0的话加上数组的长度就可以了

  • 然后返回index这一项使用的是Reflect.get(target, index)。什么?为什么不直接用target[index]?当然这样也可以,对比target[index]的区别就是Reflect.get(target, index)如果传入的target不是一个Object的话(数组也属于对象),就会报一个类型错误TypeError,而target[index]返回的会是一个undefined。比如这样:

    var obj = 5
    console.log(obj['b']) // undefined
    console.log(Reflect.get(obj, 'b')) // Uncaught TypeError: Reflect.get called on non-object

扩展点:

呆呆这边主要是想扩展一下get的第三个参数receiver(接受者),在MDN上的解释是:

如果target对象中指定了getterreceiver则为getter调用时的this值。

来看个例子理解一下😊。

案例一

例如我们开始有这么一个对象:

var obj = {
  fn: function () {
    console.log('lindaidai')
  }
}

现在使用Proxy来赋值到obj1中:

var obj = {
  fn: function () {
    console.log('lindaidai')
  }
}
var obj1 = new Proxy(obj, {
  get (target, key, receiver) {
    console.log(receiver === obj1) // true
    console.log(receiver === target) // false
    return target[key]
  }
})
obj1.fn()

可以看到,receiver表示的是obj1这个新的代理对象,target表示的是被代理的对象obj

所以,receiver可以表示使用代理对象本身

案例二

另一种情况,receiver也可以表示是从其继承的对象

var proxy = new Proxy({}, {
  get (target, key, receiver) {
    return receiver;
  }
})
console.log(proxy.getReceiver === proxy) // true
var inherits = Object.create(proxy)
console.log(inherits.getReceiver === inherits) // true

这个案例中,我新建了一个空对象的代理对象proxy,使用proxy.getReceiver获取它的receiver,发现它就是代理对象本身。

而如果我将这个代理对象作为一个原型对象,创建出一个新的对象inherits,也就是实现了原型式继承,那么这时候receiver的值就是这个被继承的对象inherits

总结

  • 可以使用Proxy代理,来改变每次获取数组的值。
  • Proxyget中,第二个参数是字符串,即使传入的是数组下标。
  • 对比于Number()的好处就是Number(undefined)会转换为NaN;但是使用~~能保证一直是数字,~~undefined === 0
  • 对比target[index]的区别就是Reflect.get(target, index)如果传入的target不是一个Object的话(数组也属于对象),就会报一个类型错误TypeError,而target[index]返回的会是一个undefined
  • Proxyget中,第三个参数receiver通常为使用代理对象本身或从其继承的对象。