去往js函数式编程(8)完

冻结

  如果我们希望避免程序员意外或故意修改对象的可能性,冻结对象是一个有效的解决方案。在对象被冻结之后,任何修改它的尝试都会静默失败。javascript 不会报告错误或抛出异常,但也不会修改对象。这种解决方案只有一个问题:冻结对象是一个浅层操作,它仅冻结属性本身,类似于 const 声明的作用。如果任何属性本身是对象或数组,并且具有进一步的对象或数组作为属性,依次类推,它们仍然可以被修改。

let myObj3 = {
  d: 22,
  m: 9,
  o: { c: 'MVD', i: 'UY', f: { a: 56 } }
}

Object.freeze(myObj3)
console.log(myObj3) // {d:22, m:9, o:{c:"MVD", i:"UY", f:{ a:56}}}

//这只是部分成功,当我们尝试更改一些属性时可以看到:
myObj3.d = 8888 //不起作用,与之前一样
myObj3.o.f.a = 9999 //糟糕,起作用了!
console.log(myObj3) // {d:22, m:9, o:{c:"MVD", i:"UY", f:{ a:9999}}}

  如果我们想要实现真正的不可变性,我们需要编写一个可以冻结对象所有层级的例程。幸运的是,通过应用递归,很容易实现这一点。主要思想是先冻结对象本身,然后递归冻结每个属性。我们必须确保只冻结对象自身的属性,不要干扰对象的原型。

const deepFreeze = (obj) => {
  if (obj && typeof obj === 'object' && !Object.isFrozen(obj)) {
    Object.freeze(obj)
    Object.getOwnPropertyNames(obj).forEach((prop) => deepFreeze(obj[prop]))
  }
  return obj
}

  如果一个对象包含对自身的引用会发生什么?如果我们跳过冻结已经被冻结的对象,我们可以避免这个问题:由于它们所引用的对象已经被冻结,因此后向循环引用将被忽略。

  如果禁止修改对象,那么你必须创建一个新对象。例如,如果你使用 Redux,reducer 是一个函数,它接收当前状态和一个动作(本质上是一个带有新数据的对象),并生成新的状态。为了结束这一切,我们还应该冻结返回的对象,就像我们对原始状态做的那样。但让我们从头开始:如何克隆一个对象?

let oldObject = {
  d: 22,
  m: 9,
  o: { c: 'MVD', i: 'UY', f: { a: 56 } }
}

let newObject = {
  d: oldObject.d,
  m: oldObject.m,
  o: { c: oldObject.o.c, i: oldObject.o.i, f: { a: oldObject.o.f.a } }
}

  你可以使用 Object.assign()或扩展运算符来创建浅复制对象:要创建再次是浅数组的副本,你可以使用 slice()或扩展运算符,如果一个对象或数组包含对象,我们会遇到冻结时的相同问题:对象是按引用复制的,这意味着在新对象中的更改也会影响到旧对象。

let newObject1 = Object.assign({}, myObj)
let newObject2 = { ...myObj }

let myArray = [1, 2, 3, 4]
let newArray1 = myArray.slice()
let newArray2 = [...myArray]

let oldObject = {
  d: 22,
  m: 9,
  o: { c: 'MVD', i: 'UY', f: { a: 56 } }
}
let newObject = Object.assign({}, oldObject)
newObject.d = 8888;
new newObject.o.f.a = 9999;

console.log(newObject);
// {d:8888, m:9, o: {c:"MVD", i:"UY", f: {a:9999}}} -- ok

console.log(oldObject);
// {d:22, m:9, o: {c:"MVD", i:"UY", f: {a:9999}}} -- oops!!

  在这种情况下,请注意当我们更改 newObject 的某些属性时会发生什么。更改 newObject.d 是可以的,但更改 newObject.o.f.a 也会影响到 oldObject,因为 newObject.o 和 oldObject.o 实际上是对同一个对象的引用。对于有一个简单的基于 JSON 的解决方案。如果我们对原始对象进行 stringify(),然后解析 parse()结果,我们将得到一个与旧对象完全独立的新对象:

const jsonCopy = (obj) => JSON.parse(JSON.stringify(obj))

  使用 JSON.stringify(),我们可以将对象转换为字符串。然后,JSON.parse()会将该字符串创建一个对象;简单!这适用于数组和对象,但存在一个问题。如果对象的任何属性具有构造函数,它们不会被调用:结果始终由普通的 js 对象组成。

let myDate = new Date()
let newDate = jsonCopy(myDate)
console.log(typeof myDate, typeof newDate)

  模式通常以四个基本要素描述:简洁的名称,用于描述问题,解决方案和后果。模式适用的上下文:需要解决方案的特定情况,可能还有一些额外的条件必须满足。解决方案:列出解决给定情况所需的元素。应用模式的后果。你可能从解决方案中获得一些收益,但也可能带来一些损失。

检测多次点击

  假设出于某种原因,你决定用户应该能够多次点击某个元素,并且点击次数在某种程度上具有意义并产生某种特殊结果。浏览器非常擅长检测单机或双击,但三次火更多次点击并不那么容易获得。

<html>
  <head>
    <title>多次点击示例</title>
    <script type="text/javascript" src="rxjs.umd.js"></script>
  </head>
  <body>
    <span id="mySpan">多次点击此文本(快速点击)</span>
    <script>
      const { fromEvent, pipe } = rxjs;
      const { buffer, filter } = rxjs.operators;

      const spanClick$ = fromEvent(
      document.getElementById("mySpan"),
      "click"
      );

      spanClick$
      .pipe(
      buffer(spanClick$.pipe(debounceTime(250))),
      map(list =>list.length),
      filter(x =>x >= 3)
      )
      .subscribe(e =>{
      console.log(${e} 次点击于 ${new Date()});
      });
    </script>
  </body>
</html>

不写咯,换本书看看

posted @ 2023-06-28 10:40  艾路  阅读(7)  评论(0编辑  收藏  举报