Javscript

JavaScript’te Proxy Modelinin Gücü – JSManifest

Kariyerimin daha sonraki bir aşamasında öğrendiğim daha ilginç kalıplardan biri, Proxy.

Proxy modelinin örneklerini aradığınızda, genellikle farklı uygulama varyasyonları görebilirsiniz. Bunun nedeni, Proxy’nin tek bir kullanım durumuyla sınırlı olmamasıdır. Bir vekil doğrulayıcı olarak hareket edebilirken diğeri performansı iyileştirme vb. ile daha fazla ilgilenebilir.

Buradaki fikir, bir proxy kullanarak, orijinal ile aynı işlevi gören mevcut nesneleri, yöntemlerinin (hatta özelliklerinin) tamamen aynı olduğu, sarmalanmış yöntemlerin içine ek mantık ekleyene kadar sarmamızdır. sarılmış işlev çağrılmadan önce. Bu tamamen dış dünyaya gizlenmiş bir süreçtir ve bu çağrı arayan kişiye her zaman aynı görünecektir.

Başka bir deyişle, proxy, bir nesnenin istemcisi ile gerçek nesnenin kendisi arasında oturur. Burası bir “koruyucu” olarak hareket etmeyi veya aşağıdaki gibi özel bir mantık eklemeyi seçebileceği yerdir. Önbelleğe almak arayan bunu hiç bilmeden. Bu nedenle bazen Arabulucu olarak anılabilir. Bazıları bunu Dekoratör deseninin başka bir biçimi olarak da sınıflandırabilir, ancak bazı farklılıklar vardır.

Bu yazıda JavaScript’teki Proxy Tasarım Modelinin gücünü gözden geçireceğiz ve bir sonraki uygulamanız için ne kadar faydalı olabileceğine dair birkaç örneği gözden geçireceğiz.

JavaScript yerel olarak bir Proxy kalıbı uygulayan sınıf, doğrudan Proxy birkaç Vanilya uygulamasından sonra kalıbı göstermek yerine sınıf.

Dekoratör ve Proxy arasındaki fark

Dekoratör deseninde, dekoratörün ana sorumluluğu sardığı (veya “dekorasyon yaptığı”) nesneyi geliştirmektir, oysa proxy daha fazla erişilebilirliğe sahiptir ve nesneyi kontrol eder.

Proxy, sardığı nesneyi geliştirmeyi veya dış dünyadan erişimi kısıtlamak gibi başka yollarla kontrol etmeyi seçebilir, ancak bunun yerine bir dekoratör geliştirmeleri bilgilendirir ve uygular.

Sorumluluk açısından fark açıktır. Mühendisler genellikle dekoratörleri yeni davranış eklemek için veya gelişmiş bir arabirim döndürdükleri eski veya eski sınıflar için bir bağdaştırıcı biçimi olarak kullanır. müşterinin bildiği ama aynı zamanda umursamadığı. Proxy’nin genellikle geri dönmesi amaçlanır istemcinin aynı nesneyle dokunulmadan çalıştığını varsayabileceği aynı arabirim.

Doğrulayıcı/Yardımcı

Burada göstereceğim bir Proxy modelinin ilk uygulaması bir doğrulayıcı olacaktır.

Bu örnek, girdiyi doğrulamaya ve özelliklerin yanlış veri türlerine ayarlanmasını önlemeye yardımcı olmak için uygulanan kalıbı gösterir. Arayanın her zaman orijinal nesneyle çalıştığını varsayması gerektiğini unutmayın, bu nedenle Proxy, sardığı nesnenin imzasını veya arabirimini değiştirmemelidir:

class Pop {
  constructor(...items) {
    this.id = 1
  }
}

const withValidator = (obj, field, validate) => {
  let value = obj[field]

  Object.defineProperty(obj, field, {
    get() {
      return value
    },
    set(newValue) {
      const errMsg = validate(newValue)
      if (errMsg) throw new Error(errMsg)
      value = newValue
    },
  })

  return obj
}

let mello = new Pop(1, 2, 3)

mello = withValidator(mello, 'id', (newId) => {
  if (typeof newId !== 'number') {
    return `The id ${newId} is not a number. Received ${typeof newId} instead`
  }
})

mello.id = '3'

Bu örnek, bir nesnenin alanlarını doğrulayan basit bir yardımcıyı gösterir. TypeError doğrulama başarısız olduğunda istisna.

Proxy’nin sahipliğini alır getter ve setter arasında id özellik ve ayarlanmaya çalışılan değerlere izin vermeyi veya reddetmeyi seçer.

İçinde Proxy sınıf, bunun gibi bir şeyle uygulanabilir:

const withValidator = (obj, field, validate) => {
  return new Proxy(obj, {
    set(target, prop, newValue) {
      if (prop === field) {
        const errMsg = validate(newValue)
        if (errMsg) throw new TypeError(errMsg)
        target[prop] = newValue
      }
    },
  })
}

let mello = new Pop(1, 2, 3)

mello = withValidator(mello, 'id', (newId) => {
  if (typeof newId !== 'number') {
    return `The id ${newId} is not a number. Received ${typeof newId} instead`
  }
})

mello.id = '3'

Doğrulayıcı mükemmel çalışıyor:

TypeError: The id 3 is not a number. Received string instead

Pano Çoklu Dolgu

Bu bölüm, metin seçimlerini kullanıcı panosuna kopyalarken, tarayıcının aşağıdakileri desteklemesini sağlayarak eski tarayıcıları desteklemenin bir yolu olarak Proxy’yi kullanmaya devam edecektir. Navigator.clipboard API. Olmazsa, kullanıma geri dönecektir. execCommand Seçimi kopyalamak için

Yine, istemci her zaman metotları çağırdığı nesnenin orijinal nesne olduğunu varsayar ve yalnızca adı geçen yöntemi çağırdığını bilir:

const withClipboardPolyfill = (obj, prop, cond, copyFnIfCond) => {
  const copyToClipboard = (str) => {
    if (cond()) {
      copyFnIfCond()
    } else {
      const textarea = document.createElement('textarea')
      textarea.value = str
      textarea.style.visibility = 'hidden'
      document.body.appendChild(textarea)
      textarea.select()
      document.execCommand('copy')
      document.body.removeChild(textarea)
    }
  }
  obj[prop] = copyToClipboard
  return obj
}

const api = (function () {
  const o = {
    copyToClipboard(str) {
      return navigator.clipboard.writeText(str)
    },
  }
  return o
})()

let copyBtn = document.createElement('button')
copyBtn.id = 'copy-to-clipboard'
document.body.appendChild(copyBtn)

copyBtn.onclick = api.copyToClipboard

copyBtn = withClipboardPolyfill(
  copyBtn,
  'onclick',
  () => 'clipboard' in navigator,
  api.copyToClipboard,
)

copyBtn.click()

Uygulamayı doğrudan gerçek içine kodlamak yerine, bu gibi durumlarda proxy’yi uygulamanın amacının ne olduğunu sorabilirsiniz. copyToClipboard işlev. Bir proxy kullanırsak, onu bağımsız olarak yeniden kullanabilir ve uygulamayı şu şekilde serbestçe değiştirebiliriz: kontrolün tersine çevrilmesi.

Bu stratejiyi kullanmanın bir başka yararı da orijinal işlevi değiştirmememizdir.

Önbellek (Performansı artırma)

Önbelleğe alma, birçok farklı senaryoda birçok farklı biçimde olabilir. Örneğin, bir Yeniden Doğrulanırken Bayat http istekleri için, nginx içerik önbelleğe alma, işlemci önbelleğe alma, tembel yükleme önbelleğe alma, not alma. vb.

JavaScript’te bir Proxy yardımıyla önbelleğe almayı da başarabiliriz.

Proxy modelini doğrudan kullanmadan uygulamak için Proxy sınıf böyle bir şey yapabiliriz:

const simpleHash = (str) =>
  str.split('').reduce((acc, str) => (acc += str.charCodeAt(0)), '')

const withMemoization = (obj, prop) => {
  const origFn = obj[prop]
  const cache = {}

  const fn = (...args) => {
    const hash = simpleHash(args.map((arg) => String(arg)).join(''))
    if (!cache[hash]) cache[hash] = origFn(...args)
    return cache[hash]
  }

  Object.defineProperty(obj, prop, {
    get() {
      return fn
    },
  })

  return obj
}

const sayHelloFns = {
  prefixWithHello(str) {
    return `[hello] ${str}`
  },
}

const enhancedApi = withMemoization(sayHelloFns, 'prefixWithHello')
enhancedApi.prefixWithHello('mike')
enhancedApi.prefixWithHello('sally')
enhancedApi.prefixWithHello('mike the giant')
enhancedApi.prefixWithHello('sally the little')
enhancedApi.prefixWithHello('lord of the rings')
enhancedApi.prefixWithHello('lord of the rings')
enhancedApi.prefixWithHello('lord of the rings')
enhancedApi.prefixWithHello('lord of the rings')
enhancedApi.prefixWithHello('lord of the rings')

önbellek:

{
  "109105107101": "[hello] mike",
  "11597108108121": "[hello] sally",
  "109105107101321161041013210310597110116": "[hello] mike the giant",
  "115971081081213211610410132108105116116108101": "[hello] sally the little",
  "108111114100321111023211610410132114105110103115": "[hello] lord of the rings"
}

Bunu doğrudan bir Proxy sınıf düz ileri:

const withMemoization = (obj, prop) => {
  const origFn = obj[prop]
  const cache = {}

  const fn = (...args) => {
    const hash = simpleHash(args.map((arg) => String(arg)).join(''))
    if (!cache[hash]) cache[hash] = origFn(...args)
    return cache[hash]
  }

  return new Proxy(obj, {
    get(target, key) {
      if (key === prop) {
        return fn
      }
      return target[key]
    },
  })
}

bu Proxy sınıf

Birkaç barebone Proxy model uygulamasında kalıcı bir model gördük ve doğrudan Proxy sınıf. JavaScript doğrudan sağladığından Proxy dile bir nesne olarak, bu yazının geri kalanı bunu bir kolaylık olarak kullanacak.

Geri kalan tüm örnekler, Proxyancak bunun yerine sınıf sözdizimine odaklanacağız çünkü özellikle bu yazı uğruna çalışmak daha özlü ve daha kolay.

Singleton’a proxy

Bir Singleton’ı hiç duymadıysanız, bir uygulamanın ömrü boyunca zaten somutlaştırılmışsa, ilgilenilen bir nesnenin döndürülmesini ve yeniden kullanılmasını sağlayan başka bir tasarım modelidir. Pratikte büyük olasılıkla bunun bir global değişken olarak kullanıldığını göreceksiniz.

Örneğin, bir MMORPG oyunu kodluyor olsaydık ve üç sınıfımız olsaydı Equipment, Personve Warrior sadece nerede olabilir bir Warrior varlığında, kullanabiliriz construct bir örneği başlatırken ikinci argümanın içindeki işleyici yöntemi Proxy üzerinde Warrior sınıf:

class Equipment {
  constructor(equipmentName, type, props) {
    this.id = `_${Math.random().toString(36).substring(2, 16)}`
    this.name = equipmentName
    this.type = type
    this.props = props
  }
}

class Person {
  constructor(name) {
    this.hp = 100
    this.name = name
    this.equipments = {
      defense: {},
      offense: {},
    }
  }

  attack(target) {
    target.hp -= 5
    const weapons = Object.values(this.equipments.offense)
    if (weapons.length) {
      for (const weapon of weapons) {
        console.log({ weapon })
        target.hp -= weapon.props.damage
      }
    }
  }

  equip(equipment) {
    this.equipments[equipment.type][equipment.id] = equipment
  }
}

class Warrior extends Person {
  constructor() {
    super(...arguments)
  }

  bash(target) {
    target.hp -= 15
  }
}

function useSingleton(_Constructor) {
  let _warrior

  return new Proxy(_Constructor, {
    construct(target, args, newTarget) {
      if (!_warrior) _warrior = new Warrior(...args)
      return _warrior
    },
  })
}

const WarriorSingleton = useSingleton(Warrior)

Birden çok örneğini oluşturmaya çalışırsak Warrior her seferinde yalnızca ilk oluşturulanın kullanılması sağlanır:

const mike = new WarriorSingleton('mike')
const bob = new WarriorSingleton('bob')
const sally = new WarriorSingleton('sally')

console.log(mike)
console.log(bob)
console.log(sally)

Sonuç:

Warrior {
  hp: 100,
  name: 'mike',
  equipments: { defense: {}, offense: {} }
}
Warrior {
  hp: 100,
  name: 'mike',
  equipments: { defense: {}, offense: {} }
}
Warrior {
  hp: 100,
  name: 'mike',
  equipments: { defense: {}, offense: {} }
}

çerez hırsızı

Bu bölümde, bir kullanarak bir örnek göstereceğiz Proxy çerez listesinden mutasyonları önlemek için. Bu, orijinal nesnenin mutasyona uğramasını ve mutatörün ( CookieStealer) kötü operasyonlarının başarılı olduğunu varsayacaktır.

Şu örneğe bir göz atalım:

class Food {
  constructor(name, points) {
    this.name = name
    this.points = points
  }
}

class Cookie extends Food {
  constructor() {
    super(...arguments)
  }

  setFlavor(flavor) {
    this.flavor = flavor
  }
}

class Human {
  constructor() {
    this.foods = []
  }

  saveFood(food) {
    this.foods.push(food)
  }

  eat(food) {
    if (this.foods.includes(food)) {
      const foodToEat = this.foods.splice(this.foods.indexOf(food), 1)[0]
      this.hp += foodToEat.points
    }
  }
}

const apple = new Food('apple', 2)
const banana = new Food('banana', 2)

const chocolateChipCookie = new Cookie('cookie', 2)
const sugarCookie = new Cookie('cookie', 2)
const butterCookie = new Cookie('cookie', 3)
const bakingSodaCookie = new Cookie('cookie', 3)
const fruityCookie = new Cookie('cookie', 5)

chocolateChipCookie.setFlavor('chocolateChip')
sugarCookie.setFlavor('sugar')
butterCookie.setFlavor('butter')
bakingSodaCookie.setFlavor('bakingSoda')
fruityCookie.setFlavor('fruity')

const george = new Human()

george.saveFood(apple)
george.saveFood(banana)
george.saveFood(chocolateChipCookie)
george.saveFood(sugarCookie)
george.saveFood(butterCookie)
george.saveFood(bakingSodaCookie)
george.saveFood(fruityCookie)

console.log(george)

George’un yemeği:

 {
  foods: [
    Food { name: 'apple', points: 2 },
    Food { name: 'banana', points: 2 },
    Cookie { name: 'cookie', points: 2, flavor: 'chocolateChip' },
    Cookie { name: 'cookie', points: 2, flavor: 'sugar' },
    Cookie { name: 'cookie', points: 3, flavor: 'butter' },
    Cookie { name: 'cookie', points: 3, flavor: 'bakingSoda' },
    Cookie { name: 'cookie', points: 5, flavor: 'fruity' }
  ]
}

somutlaştırdık george kullanmak Human sınıfı ve deposuna 7 adet yiyecek ekledik. George meyvelerini ve kurabiyelerini yemek üzere olduğu için mutludur. Kurabiyeleri konusunda özellikle heyecanlı çünkü en sevdiği lezzetlerin hepsini aynı anda alıyor ve kısa süre sonra kurabiye ihtiyacını gidermek için onları yemeye başlıyor.

Ancak bir sorun var:

const CookieStealer = (function () {
  const myCookiesMuahahaha = []

  return {
    get cookies() {
      return myCookiesMuahahaha
    },
    isCookie(obj) {
      return obj instanceof Cookie
    },
    stealCookies(person) {
      let indexOfCookie = person.foods.findIndex(this.isCookie)
      while (indexOfCookie !== -1) {
        const food = person.foods[indexOfCookie]
        if (this.isCookie(food)) {
          const stolenCookie = person.foods.splice(indexOfCookie, 1)[0]
          myCookiesMuahahaha.push(stolenCookie)
        }
        indexOfCookie = person.foods.findIndex(this.isCookie)
      }
    },
  }
})()

CookieStealer.stealCookies(george)

bu CookieStealer kurabiyelerini çalmak için birdenbire ortaya çıkar. bu CookieStealer şimdi deposunda 5 çerez var:

[
  Cookie { name: 'cookie', points: 2, flavor: 'chocolateChip' },
  Cookie { name: 'cookie', points: 2, flavor: 'sugar' },
  Cookie { name: 'cookie', points: 3, flavor: 'butter' },
  Cookie { name: 'cookie', points: 3, flavor: 'bakingSoda' },
  Cookie { name: 'cookie', points: 5, flavor: 'fruity' }
]

George:

Human {
  foods: [
    Food { name: 'apple', points: 2 },
    Food { name: 'banana', points: 2 }
  ]
}

Geriye sarılıp kurtarıcımızı tanıtacak olsaydık Superman uygulayan yöntemlerinden birini uygulamak Proxy önlemek için desen CookieStealer onun kötü eylemlerinden sorunumuzu çözecekti:

class Superman {
  protectFromCookieStealers(obj, key) {
    let realFoods = obj[key]
    let fakeFoods = [...realFoods]

    return new Proxy(obj, {
      get(target, prop) {
        if (key === prop) {
          fakeFoods = [...fakeFoods]

          Object.defineProperty(fakeFoods, 'splice', {
            get() {
              return function fakeSplice(...[index, removeCount]) {
                fakeFoods = [...fakeFoods]
                return fakeFoods.splice(index, removeCount)
              }
            },
          })

          return fakeFoods
        }
        return target[prop]
      },
    })
  }
}

const superman = new Superman()
const slickGeorge = superman.protectFromCookieStealers(george, 'foods')

Bizim arkadaşımız superman şans eseri olur protectFromCookieStealers gücünü kullanarak Proxy ile numara yapmak çerez listesi! o tutar gerçek George’un kurabiyelerinden saklanan yiyeceklerin koleksiyonu. CookieStealer. CookieStealer şeytani planlarıyla ilerliyor ve görünüşe göre kandırılmış kurabiyelerden kurtulduğunu düşünerek:

CookieStealer.stealCookies(slickGeorge)

console.log(CookieStealer.cookies)

bu CookieStealer hala deposunda kurabiyelerle uzaklaşıyor ve düşünüyor ondan uzaklaştı:

[
  Cookie { name: 'cookie', points: 2, flavor: 'chocolateChip' },
  Cookie { name: 'cookie', points: 2, flavor: 'sugar' },
  Cookie { name: 'cookie', points: 3, flavor: 'butter' },
  Cookie { name: 'cookie', points: 3, flavor: 'bakingSoda' },
  Cookie { name: 'cookie', points: 5, flavor: 'fruity' }
]

Süpermen tarafından kandırıldığını çok az biliyor ve bunlar sahte kurabiyeler! george gücü sayesinde hala kurabiyelerine dokunulmamış durumda. Proxy onu kötülüğün karanlığından kurtarmak:

Human {
  foods: [
    Food { name: 'apple', points: 2 },
    Food { name: 'banana', points: 2 },
    Cookie { name: 'cookie', points: 2, flavor: 'chocolateChip' },
    Cookie { name: 'cookie', points: 2, flavor: 'sugar' },
    Cookie { name: 'cookie', points: 3, flavor: 'butter' },
    Cookie { name: 'cookie', points: 3, flavor: 'bakingSoda' },
    Cookie { name: 'cookie', points: 5, flavor: 'fruity' }
  ]
}

Çözüm

Umarım bu, Proxy modeline ve şimdi yerleşik olarak bu konseptten nasıl yararlanılacağına biraz ışık tutmaya yardımcı olmuştur. Proxy JavaScript’te sınıf.

Bu yazının sonu böyle bitiyor 🙂 Umarım bu makaleyi sizin için yararlı bulmuşsunuzdur ve gelecekteki gönderiler için beni medyadan takip ettiğinizden emin olun!

İlgili Makaleler

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Başa dön tuşu