Интервьюер: Пожалуйста, создайте класс в ES6 для реализации приватных свойств

ECMAScript 6
Интервьюер: Пожалуйста, создайте класс в ES6 для реализации приватных свойств

введение

в предыдущем постеИнтервьюер: Расскажите мне о принципе реализации входа в WeChat по скан-коду?После публикации статьи я не ожидал много лайков.В сообществе Nuggets так много front-end партнеров.Давайте в будущем будем более активными в сообществе Nuggets.Кратко расскажу о серии рубрик,которые я буду организовать:

раньше, былCSDNплатформа для публикации блогов,Чаойи の Блог по технологиям обучения, я обнаружил, что фронтальная активность не очень высока, но в сообществе Nuggets я увидел серию отличных статей с тысячами лайков и сотнями тысяч посещений, и качество статей действительно высокое, я могу научиться много Знаний.

Здесь я исследую проблему, смотрю на проблему с любопытством и пытаюсь представить ее всем в кратких и простых для понимания словах.Возможность учить других также весьма полезна и полезна для меня.


Здесь я хотел бы поделиться следующими правилами, которые люди помнят из лекции преподавателя управления ИТ-проектами в прошлом семестре:

  • 5% того, что слышно
  • 10% прочитанного
  • просмотрено 30% контента
  • 50% того, что обсуждалось
  • 75% контента сделано лично
  • 90% того, что было преподано другим

Если это поможет вам, пожалуйстаОдна кнопка три ссылки, Конечно, если в этой статье есть какие-либо проблемы, читатели могут меня поправить, это также процесс обучения, спасибо~

возвращаемый текст

Спешите вернуться к теме, этот вопрос такой же, как и в предыдущем блоге, он тоже был задан в августе этого года, в то время я знал о классе, и я видел некоторые знания о наследовании классов в сообществе, но я особо не трогал Код, до сих пор помню, что сцена диалога в то время была такая:

Интервьюер: Вы должны знать ES6, верно? (Конечно), ну тогда вы знаете, что в ES6 есть класс, вы можете спроектировать и реализовать его приватные свойства?

Я: эммм (я думал об этом в это время, кажется, я могу сделать это с замыканиями), могу ли я использовать идею замыканий, чтобы сделать это?

Интервьюер: Конечно (покажите мне код)

Итак, я написал этот код:

class classA{
	// xxx省略
	let fun = function () {
  		var a = 0;
  		return function () {
    		console.log(++a);
  		}
	}
	// xxx省略
}

Небольшая часть кода опущена, но в целом код похож на приведенный выше код. Оглядываясь назад, я понимаю, что в то время это было действительно смешно. Неудивительно, что интервьюер остановился и синтаксис был неправильным. При этом, когда я ее писал, я был в оцепенении, потому что код синтаксиса был не очень знаком, но сегодня я решил эту проблему с любопытством.

Почему появляется класс

На самом деле научилсяjavaприятель должен быть правclassЯ с этим не знаком. Во время учебы на втором и младшем курсах я также былjavaКод пишется и пишется. так почемуJSбыть представленным вclassШерстяная ткань?

До es6, хотя JS и Java были языками ООП (объектно-ориентированными), в JS существовала только концепция объектов и не было классов.

Появление класса в es6 сократило дистанцию ​​между JS и традиционными языками ООП. Однако это лишьсинтаксический сахарНу, он не может достичь та же функций, что и традиционные языки ООП. Среди них один из самых больших болевых точек - проблема частных атрибутов.

Что такое частная собственность?

Частные свойства являются очень распространенной функцией в объектно-ориентированном программировании (ООП) и обычно соответствуют следующим характеристикам:

  • Можно получить доступ различными методами внутри класса, но не вне класса;
  • Подклассы не могут наследовать частные свойства суперклассов.

В Java вы можете использоватьprivateРеализовать приватные переменные, но, к сожалению, в JS эта функция недоступна.

Предложение для частной собственности

В июне 2015 года в качестве стандарта был выпущен ES6.В ознаменование этого исторического момента этот стандарт также называется ES2015.До сих пор класс в JavaScript был изменен с запасного колеса. Однако проблема частной собственности не была решена, и возникло предложение - добавить перед названием собственности#, используемый для представления частных свойств.

class Foo {
  #a;  // 定义私有属性
  constructor(a, b) {
    this.#a = a;
    this.b = b
  }
}

Объявление частных свойств приведенного выше кода должно быть скомпилировано компилятором, таким как Babel, прежде чем его можно будет использовать в обычном режиме.

А почему бы и нетprivateКак насчет ключевых слов? Ссылаясь на то, что сказал большой парень, это то, что одной из основных причин является сближение с Python, В конце концов, JS развивается в сторону Python с es6.

Как спроектировать и реализовать частную собственность?

Выше мы ввели причину Class, и это не решило проблему частных свойств, тогда мы использовалиJSerКак вы сами его проектируете? Разберемся с любопытством:

соглашение об именовании

В настоящее время наиболее распространенный метод:соглашение об именовании, так как это еще не решено, мы не можем определить его сами, можно ли использовать его как частное свойство для специального имени? Если все будут следовать этой норме, разве эта проблема не будет решена?

/* 约定命名 */
class ClassA {
  constructor(x) {
    this._x = x;
  }
  getX() {
    return this._x;
  }
}

let classa = new ClassA(1);
/* 此时可以访问我们自定义私有属性命名的_x */
console.log(classa._x); // 1
console.log(classa.getX()); // 1

Очевидно, что вышеописанный метод прост и удобен, каждый может следовать спецификации, а лучше читать чужой код.

Закрытие

Одним из преимуществ замыканий является то, что они могут защищать внутренние свойства, чего я и хотел добиться в самом начале.constructorВ области следующий код:

/* 闭包 */
class ClassB {
  constructor(x) {
    let _x = x;
    this.getX = function(){
      return _x;
    }
  }
}
let classb = new ClassB(1);
/* 此时不可以访问我们自定义私有属性命名的_x */
console.log(classb._x); // undefined
console.log(classb.getX()); // 1

Очевидно, что если приватных свойств становится все больше и больше, он будет выглядеть раздутым, вызывая определенные затруднения при последующем обслуживании, и читать другим будет не очень дружелюбно. В то же время методы, которые ссылаются на закрытые переменные, не могут быть определены в цепочке прототипов.

Расширенное закрытие

в состоянии пройтиIIFE(Немедленная функция исполнения. Выражение) Установление закрытия, в котором установление переменной и класса, ссылочные переменные для достижения частных переменных через класс.

/* 进阶版闭包 */
const classC = (function () {
  let _x;

  class ClassC {
    constructor(x) {
      _x = x;
    }
    getX() {
      return _x;
    }
  }
  return ClassC;
})();

let classc = new classC(3);
/* 此时不可以访问我们自定义私有属性命名的_x */
console.log(classc._x); // undefined
console.log(classc.getX()); // 3

Таким образом, это немногомодульныйИдею модульности рекомендую в этой предыдущей статье:

«Устранение утечек и заполнение недостатков» Углубленный анализ модульности JavaScript ES5/AMD/CMD/COMMONJS/ES6 (необходимо для повышения заработной платы) | Технические документы Nuggets — специальный двойной раздел


Проблемы, возникающие в связи с приближением замыканий?

Выше мы использовали замыкания и расширенные замыкания для решения проблемы приватных свойств, но это проблематично.В качестве примера возьмем расширенные замыкания:

/* 进阶版闭包带来的问题 */
const classC = (function () {
  let _x;

  class ClassC {
    constructor(x) {
      _x = x;
    }
    getX() {
      return _x;
    }
  }
  return ClassC;
})();

let classc1 = new classC(3);
/* 此时不可以访问我们自定义私有属性命名的_x */
console.log(classc1._x); // undefined
console.log(classc1.getX()); // 3

/* 问题引出:此时新创建一个实例 */
let classc2 = new classC(4);
/* 出现了问题:实例之间会共享变量 */
console.log(classc1.getX()); // 4

Из вышеприведенного кода видно, что нельзя создавать приватные переменные с замыканиями, переменные будут общими между инстансами, как если бы их инстанцировало несколько человек, но операция все равно остается одним и тем же свойством, что явно нежелательно.

Symbol

использоватьSymbolпеременные могут быть объектамиkeyФункции, которые мы можем смоделировать, чтобы реализовать более реалистичные частные свойства.

/* Symbol */
const classD = (function () {
  const _x = Symbol('x');
  class ClassD {
    constructor(x) {
      this[_x] = x;
    }
    getX() {
      return this[_x];
    }
  }
  return ClassD;
})();

let classd = new classD(4);
/* 此时不可以访问我们自定义私有属性命名的_x */
console.log(classd._x); // undefined
console.log(classd.getX()); // 4
classd[_x] = 1;
console.log(classd[_x]); // ReferenceError: _x is not defined

Что касается приведенного выше кода, я ссылаюсь на ответ в области комментариев внизу статьи:

Sysmol работает с синтаксисом шаблонов импорта/экспорта. Например, в A.js вы определяете класс A и Symbol (просто используйте свой метод написания) и предоставляете внешнему миру только класс A. Затем введите экземпляр класса A в другие файлы js, значение Symbol не может быть получено, а имя переменной не может быть доступно через '.' (Symbol уникален и не может быть получен без раскрытия внешнего мира). Это личное.

Через шаблонную перспективу мы выставляемClassD,SymbolЕдинственный, он не будет разоблачен, и внешний мир не сможет его получить, но это не лишено недостатков, см. следующий код:

console.log(classd[Object.getOwnPropertySymbols(classd)[0]]); // 4

Оказывается, ES6Object.getOwnPropertySymbolsВы можете получить атрибут символа, и сегодня я узнал кое-что новое(*^▽^*)

Чтобы решить вышеуказанную проблему, мы должны ввести новую вещь:WeakMap

WeakMap

/* WeakMap  */
const classE = (function () {
  const _x = new WeakMap();
  class ClassE {
    constructor(x) {
      _x.set(this, x);
    }
    getX() {
      return _x.get(this);;
    }
  }
  return ClassE;
})();

let classe = new classE(5);
/* 此时不可以访问我们自定义私有属性命名的_x */
console.log(classe._x); // undefined
console.log(classe.getX()); // 5

Этот метод очень хорошо решает проблему частной собственности.WeakMap 和 MapСопутствующие знания, планирую продолжить обсуждение в следующей статье, эти знания в настоящее время особо не знакомы, наверное понимаю, что их нельзя пройти, слабо ссылаться, можно обратить внимание на последующие статьи.

О дополнительных обновлениях WeakMap


Дополнительное обновление от 12 октября

Вопрос, заданный @HsuYang в области комментариев: если вы хотите поддерживать несколько частных переменных, есть ли проблемы с использованием Map здесь?

Итак, я попробовал несколько частных переменных, сначала взгляните на следующий код:

/* WeakMap  */
const classE = (function () {
  const _x = new WeakMap();
  class ClassE {
    constructor(x, y) {
      _x.set(this, x);
      _x.set(this, y);
    }
    getX() {
      return _x.get(this);;
    }
  }
  return ClassE;
})();

let classe = new classE(5, 6);
/* 此时不可以访问我们自定义私有属性命名的_x */
console.log(classe.getX()); // 6

Эй, вы нашли проблему? Последний вывод, который у нас есть, только_yПервоначально эта частная собственность появиласьпроблема покрытия, так как решить эту проблему?


Поскольку частные свойства должны быть связаны с экземплярами, возможно ли создать объект, содержащий все частные свойства для обслуживания? Таким образом, все частные свойства хранятся в нем, что решает проблему нескольких частных переменных. В то же время эта техника также полезна, то есть при прохождении свойств или выполненияJSON.stringifyЧастные свойства экземпляра не отображаются.

Но он опирается на доступный и управляемыйWeakMapПеременная.

const map = new WeakMap();
// 创建一个在每个实例中存储私有变量的对象
const internal = (obj) => {
  if (!map.has(obj)) {
    map.set(obj, {});
  }
  return map.get(obj);
}

class ClassE {
  constructor(name, age) {
    internal(this).name = name;
    internal(this).age = age;
  }
  get userInfo() {
    return '姓名:' + internal(this).name + ',年龄:' + internal(this).age;
  }
}

const classe1 = new ClassE('Chocolate', 18);
const classe2 = new ClassE('Lionkk', 19);

console.log(classe1.userInfo); // 姓名:Chocolate,年龄:18
console.log(classe2.userInfo); // 姓名:Lionkk,年龄:19
/* 无法访问私有属性 */
console.log(classe1.name); // undefined
console.log(classe2.age); // undefined

Proxy

В области комментариев друг @ shu millet предположил, что его можно использоватьПерехват настроек проксиЭтот способ сделать, теперь добавить.

Прокси — это замечательная новая функция в JavaScript, которая позволит вам эффективно обернуть объект в объект, называемый прокси, и перехватывать все взаимодействия с этим объектом. Мы будем использовать прокси и следовать вышеизложенномусоглашение об именованиидля создания закрытых переменных, но сделать доступ к этим закрытым переменным ограниченным за пределами класса.

Прокси может перехватывать множество различных типов взаимодействий, но мы хотим сосредоточиться наgetа такжеset, Proxy позволяет нам перехватывать операции чтения и записи для свойства соответственно. При создании прокси вы указываете два параметра, первый предназначен для переносаПример, а второй вы определяете, что хотите перехватывать разные методыОбъект «обработчик».

Наш процессор будет выглядеть так:

const handler = {
  get: function(target, key) {
    if (key[0] === '_') {
      throw new Error('Attempt to access private property');
    }
    return target[key];
  },
  set: function(target, key, value) {
    if (key[0] === '_') {
      throw new Error('Attempt to access private property');
    }
    target[key] = value;
  }
};

В каждом случае мы проверяем, начинается ли имя свойства, к которому осуществляется доступ, с символа подчеркивания, и если да, то выдаем ошибку, препятствующую доступу к нему.

Зарезервировано для использования вышеуказанным методомinstanceofспособность (эта проблема возникает в закрывающем элементе), но в это время возникает новая проблема:

когда мы пытаемся выполнитьJSON.stringifyПроблема возникает из-за того, что он пытается отформатировать частное свойство. Чтобы исправить это, нам нужно переписатьtoJSONфункционировать толькоВозвращает "общедоступное" свойство. Мы можем сделать это, обновив нашgetпроцессор для обработкиtoJSONКонкретный случай:

Примечание. Это переопределит любую пользовательскую функцию toJSON.

 get: function(target, key) {
  if (key[0] === '_') {
    throw new Error('Attempt to access private property');
  } else if (key === 'toJSON') {
    const obj = {};
    for (const key in target) {
      if (key[0] !== '_') {           // 只复制公共属性
        obj[key] = target[key];
      }
    }
    return () => obj;
  }
  return target[key];
}

Затем мы можем интегрировать код:

class Student {
  constructor(name, age) {
    this._name = name;
    this._age = age;
  }
  get userInfo() {
    return '姓名:' + this._name + ',年龄:' + this._age;
  }
}

const handler = {
  get: function (target, key) {
    if (key[0] === '_') { // 访问私有属性,返回一个 error
      throw new Error('Attempt to access private property');
    } else if (key === 'toJSON') {
      const obj = {};
      for (const key in target) { // 只返回公共属性
        if (key[0] !== '_') {
          obj[key] = target[key];
        }
      }
      return () => obj;
    }
    return target[key]; // 访问公共属性,默认返回
  },
  set: function (target, key, value) {
    if (key[0] === '_') {
      throw new Error('Attempt to access private property');
    }
    target[key] = value;
  }
}

const stu = new Proxy(new Student('Chocolate', 21), handler);

console.log(stu.userInfo);           // 姓名:Chocolate,年龄:21
console.log(stu instanceof Student); // true
console.log(JSON.stringify(stu));  // "{}"
for (const key in stu) {           
  console.log(key);  // _name  _age
}

Теперь мы инкапсулировали наши частные свойства, и ожидаемая функциональность все еще существует, единственное предостережение заключается в том, что наши частные свойства все еще доступны для просмотра.for(const key in stu)перечислит_nameа также_age.


Чтобы решить вышеупомянутую проблему обхода частных свойств, я думаю, что дескриптор свойства может манипулировать соответствующими свойствами объекта, а затем настраиватьenumerable , в самый разProxyможет справиться с этим, он может перехватитьgetOwnPropertyDescriptorвызывать и манипулировать выводом нашего частного свойства, код выглядит следующим образом:

getOwnPropertyDescriptor(target, key) {
  const desc = Object.getOwnPropertyDescriptor(target, key);
  if (key[0] === '_') {
    desc.enumerable = false;
  }
  return desc;
}

Для получения подробной информации см.:

Справочная документация по Object.getOwnPropertyDescriptor

Наконец-то у нас есть финальная полная версия, поздравляю(*^▽^*), код интеграции выглядит следующим образом:

class Student {
  constructor(name, age) {
    this._name = name;
    this._age = age;
  }
  get userInfo() {
    return '姓名:' + this._name + ',年龄:' + this._age;
  }
}

const handler = {
  get: function (target, key) {
    if (key[0] === '_') { // 访问私有属性,返回一个 error
      throw new Error('Attempt to access private property');
    } else if (key === 'toJSON') {
      const obj = {};
      for (const key in target) { // 只返回公共属性
        if (key[0] !== '_') {
          obj[key] = target[key];
        }
      }
      return () => obj;
    }
    return target[key]; // 访问公共属性,默认返回
  },
  set: function (target, key, value) {
    if (key[0] === '_') {
      throw new Error('Attempt to access private property');
    }
    target[key] = value;
  },
  // 解决私有属性能遍历问题,通过访问属性对应的属性描述符,然后设置 enumerable 为 false
  getOwnPropertyDescriptor(target, key) {
    const desc = Object.getOwnPropertyDescriptor(target, key);
    if (key[0] === '_') {
      desc.enumerable = false;
    }
    return desc;
  }
}

const stu = new Proxy(new Student('Chocolate', 21), handler);

console.log(stu.userInfo);           // 姓名:Chocolate,年龄:21
console.log(stu instanceof Student); // true
console.log(JSON.stringify(stu));  // "{}"
for (const key in stu) {           // No output 不能遍历私有属性
  console.log(key);
}
stu._name = 'Lionkk';                  // Error: Attempt to access private property

новый подход

С точки зрения тенденций развития, TS стал одним из необходимых навыков во фронтенде.TypeScriptЧастное — хорошее решение проблемы частной собственности, и я узнаю об этом позже.tsДобавьте позже.

Приложение: Обработка в TypeScript

TypeScript — это расширенный набор JavaScript, который компилируется в собственный JavaScript для использования в производстве. Разрешение указывать частные, общедоступные или защищенные свойства — одна из функций TypeScript.

class Student {
  private name;
  private age;

  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  get userInfo() {
    return '姓名:' + this.name + ',年龄:' + this.age;
  }
}

const stu = new Student('Chocolate', 21);
console.log(stu.userInfo);           // 姓名:Chocolate,年龄:21

При использовании TypeScript важно отметить, что он работает только тогда, когдакомпилироватьЭти типы известны только во время компиляции, а модификаторы private и public вступают в силу только во время компиляции. если вы попытаетесь получить доступstu.name, вы обнаружите, что это действительно возможно. Просто TypeScript выдаст вам ошибку при компиляции, но не остановит компиляцию.

// 编译时错误:属性 ‘name’ 是私有的,只能在 ‘Student ’ 类中访问。
console.log(stu.name); // 'Chocolate'

TypeScript не будет достаточно умен, чтобы попытаться предотвратить доступ кода к закрытым свойствам во время выполнения. Я просто перечисляю это здесь, чтобы люди поняли, что это не решает проблему напрямую.

Кроме того, частные переменные класса TypeScript окончательно компилируются черезWeakMapДля этого ответы друзей в комментариях~


На этом эта статья окончена, и последующие статьи будут ускорены и ближе, изучая и размышляя с любопытством~

Если у ваших друзей есть лучший способ, добро пожаловать в общение.Конечно, в этой статье могут быть сомнения.Вы можете меня поправить.Это также процесс обучения.Спасибо~

Особая благодарность друзьям в разделе комментариев,Класс дизайна в ES6 для реализации частных свойствЯ лучше разбираюсь в этой проблеме, спасибо Thanks♪(・ω・)ノ

Ссылка в этой статье

Говоря о частных переменных класса

Несколько методов реализации приватных свойств в ES6 Class

ES6 имитирует приватные атрибуты + поверхностный тест интерфейса 100

Спасибо за вышеуказанные статьи, уважаю плоды труда, настоящим выдвигаю ссылку на оригинальный текст.

наконец

Вывод статей не из легких, надеюсь вы поддержите волну!

Прошлые выборы:

Обратите внимание на склад передней части маленького льва

leetcode-javascript: склад решения проблем JavaScript LeetCode, маршрут решения проблем переднего плана (карта ума)

Друзья могут отправлять свой собственный код решения проблем в задачах, 🤝 Добро пожаловать в раздел «Участие», вы можете зарегистрироваться и решить проблему, Поставьте ⭐️, если этот проект помог вам!

Посетите блог Чаои, друзьям удобно читать и играть~

学如逆水行舟,不进则退