- Оригинальный адрес:Metaprogramming in ES6: Part 2 - Reflect
- Оригинальный автор:Keith Cirkel
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:yoyoyohamapi
- Корректор:IridescentMia ParadeTo
в моемПредыдущий пост в блоге, мы изучили символы и то, как они добавляют полезные функции метапрограммирования в JavaScript. На этот раз мы (наконец-то!) начнем говорить об отражении. если ты не читалЧасть 1: Символы, то я предлагаю вам сначала прочитать его. В прошлой статье я потрудился подчеркнуть один момент:
- СимволыОтражение в реализации- Вы применяете символы к своим существующим классам и объектам, чтобы изменить их поведение.
- ОтразитьРефлексия в провинции (рефлексия через самоанализ)- Часто используется для изучения очень низкоуровневой информации о коде.
- ПроксиОтражение через ходатайство- Обертывание объектов и перехват поведения объектов через ловушки.
Reflect
новый глобальный объект (что-то вродеJSON
илиMath
), который предоставляет ряд полезных методов самоанализа (самоанализ — довольно красивое слово для «посмотри на эту вещь»). Инструменты самоанализа уже существуют в JavaScript, напримерObject.keys
,Object.getOwnPropertyNames
и т.п. Итак, почему мы все еще новички в API, а не расширяемся непосредственно на Object?
"встроенный метод"
Все спецификации JavaScript и результирующий движок основаны на наборе «встроенных методов». Эти встроенные методы эффективно сообщают движку JavaScript о выполнении основных операций над объектами, разбросанными по всему коду. Если вы прочитаете спецификацию, вы обнаружите, что эти методы разбросаны повсюду, например.[[Get]]
,[[Set]]
,[[HasOwnProperty]]
и т. д. (если у вас нет терпения читать все спецификации, список этих встроенных методов находится по адресуES5 Раздел 8.12так же какES6 Раздел 9.1доступный).
Некоторые из этих «встроенных методов» скрыты от кода JavaScript, другие используются в других методах, и даже если эти методы доступны, они все равно скрыты в труднодоступных местах. Например,Object.prototype.hasOwnProperty
да[[HasOwnProperty]]
Реализация , но не все объекты наследуются от Object, поэтому иногда вам приходится писать какой-то причудливый код для использованияhasOwnProperty
, как показано в следующем примере:
var myObject = Object.create(null); // 这段代码比你想象得更加常见(尤其是在使用了新的 ES6 的类的时候)
assert(myObject.hasOwnProperty === undefined);
// 如果你想在 `myObject` 上使用 hasOwnProperty:
Object.prototype.hasOwnProperty.call(myObject, 'foo');
См. другой пример,[[OwnPropertyKeys]]
Этот встроенный метод получает все строковые и символьные ключи объекта и возвращает их в виде массива. Единственный способ получить эти ключи сразу без использования Reflect — подключитьсяObject.getOwnPropertyNames
а такжеObject.getOwnPropertySymbols
результат:
var s = Symbol('foo');
var k = 'bar';
var o = { [s]: 1, [k]: 1 };
// 模拟 [[OwnPropertyKeys]]
var keys = Object.getOwnPropertyNames(o).concat(Object.getOwnPropertySymbols(o));
assert.deepEqual(keys, [k, s]);
метод отражения
Reflection — очень полезная коллекция всех внутренних проприетарных движков JavaScript.«Внутренний метод», теперь отображается как единый удобный объект — Reflect. Вы можете спросить: «Звучит здорово, но почему бы просто не привязать встроенный метод к Object?Object.keys
,Object.getOwnPropertyNames
Итак». Сейчас я расскажу, почему:
- В Reflection есть методы не только для объектов, но и для функций, таких как
Reflect.apply
, ведь звонилObject.apply(myFunction)
Это выглядит так странно. - Использование одного объекта для хранения встроенных методов сохраняет чистоту остального JavaScript, что лучше, чем присоединение рефлексивного метода к конструктору или прототипу с помощью оператора точки, и лучше, чем непосредственное использование глобальных переменных.
-
typeof
,instanceof
так же какdelete
Уже существует как оператор отражения — добавление нового ключевого слова для той же функциональности было бы бременем для разработчика, в то же время это было бы кошмаром для обратной совместимости и увеличило бы количество зарезервированных слов в JavaScript.
Reflect.apply ( target, thisArgument [, argumentList] )
Reflect.apply
а такжеFunction#apply
Аналогично — принимает функцию, контекст для вызова функции и массив аргументов. с этого момента тыМогудуматьFunction#call
/Function#apply
устарел. Это не кардинальное изменение, но это имеет большой смысл. Показано нижеReflect.apply
Применение:
var ages = [11, 33, 12, 54, 18, 96];
// Function.prototype 风格:
var youngest = Math.min.apply(Math, ages);
var oldest = Math.max.apply(Math, ages);
var type = Object.prototype.toString.call(youngest);
// Reflect 风格:
var youngest = Reflect.apply(Math.min, Math, ages);
var oldest = Reflect.apply(Math.max, Math, ages);
var type = Reflect.apply(Object.prototype.toString, youngest);
Настоящим преимуществом перехода от Function.prototype.apply к Reflect.apply является защита: любой код может попытаться изменить значение функции.call
илиapply
метод, из-за которого вы можете застрять из-за сбоя кода или какой-то плохой ситуации. В реальном мире это не имело бы большого значения, но код, подобный следующему, действительно мог бы существовать:
function totalNumbers() {
return Array.prototype.reduce.call(arguments, function (total, next) {
return total + next;
}, 0);
}
totalNumbers.apply = function () {
throw new Error('Aha got you!');
}
totalNumbers.apply(null, [1, 2, 3, 4]); // 抛出 Error('Aha got you!');
// ES5 中保证防御性的代码看起来很糟糕:
Function.prototype.apply.call(totalNumbers, null, [1, 2, 3, 4]) === 10;
// 你也可以这样做,但看起来还是不够整洁:
Function.apply.call(totalNumbers, null, [1, 2, 3, 4]) === 10;
// Reflect.apply 会是救世主!
Reflect.apply(totalNumbers, null, [1, 2, 3, 4]) === 10;
Reflect.construct ( target, argumentsList [, constructorToCreateThis] )
похожий наReflect.apply
——Reflect.construct
Позволяет передать последовательность аргументов для вызова конструктора. Он может обслуживать класс и устанавливать правильный объект для конструктора, чтобы иметь правильныйthis
ссылка для соответствия соответствующему прототипу. В дни ES5 вы бы использовалиObject.create(Constructor.prototype)
режиме, затем передайте объект вConstructor.call
илиConstructor.apply
.Reflect.construct
Разница в том, что вам нужно передать только конструктор, а не объект --Reflect.construct
Позаботьтесь обо всем (если вы опустите третий параметр, созданный прототип объекта будет привязан по умолчанию кtarget
параметр). В предыдущем стиле завершение построения объекта было сложной задачей, но в новом стиле это можно сделать так же просто, как всего одна строка кода:
class Greeting {
constructor(name) {
this.name = name;
}
greet() {
return Hello ${this.name};
}
}
// ES5 风格的工厂函数:
function greetingFactory(name) {
var instance = Object.create(Greeting.prototype);
Greeting.call(instance, name);
return instance;
}
// ES6 风格的工厂函数:
function greetingFactory(name) {
return Reflect.construct(Greeting, [name], Greeting);
}
// 如果省略第三个参数,那么默认绑定对象原型到第一个参数
function greetingFactory(name) {
return Reflect.construct(Greeting, [name]);
}
// ES6 下顺滑无比的线性工厂函数:
const greetingFactory = (name) => Reflect.construct(Greeting, [name]);
Reflect.defineProperty ( target, propertyKey, attributes )
Reflect.definedProperty
В основном из-заObject.defineProperty
- Это позволяет вам определить метаинформацию об атрибуте. В сравнении сObject.defineProperty
,Reflect.defineProperty
Это более уместно, потому что Obejct.* подразумевает, что он действует на литерал объекта (в конце концов, Object является конструктором литерала объекта), тогда как Reflect.defineProperty подразумевает только то, что вы выполняете отражение, что более семантично.
На что следует обратить внимание, так это на то, чтоReflect.defineProperty
- так какObject.defineProperty
то же - для инвалидаtarget
, такие как числовые или строковые примитивные значения (Reflect.defineProperty(1, 'foo')
), выкинетTypeError
. Вместо того, чтобы молча терпеть неудачу, лучше выдать ошибку, чтобы привлечь ваше внимание, когда параметр имеет неправильный тип.
Опять же, вы можете думать, чтоObject.defineProperty
Отныне устарело, и используйтеReflect.defineProperty
заменять:
function MyDate() {
/*…*/
}
// 老的风格下,我们使用 Object.defineProperty 来定义一个函数的属性,显得很奇怪
// (为什么我们不用 Function.defineProperty ?)
Object.defineProperty(MyDate, 'now', {
value: () => currentms
});
// 新的风格下,语义就通畅得多,因为 Reflect 只是在做反射。
Reflect.defineProperty(MyDate, 'now', {
value: () => currentms
});
Reflect.getOwnPropertyDescriptor ( target, propertyKey )
Как и выше, мы предпочитаем использоватьReflect.getOwnPropertyDescriptor
заменятьObject.getOwnPropertyDescriptor
для получения метаинформации дескриптора атрибута. а такжеObject.getOwnPropertyDescriptor(1, 'foo')
молча потерпит неудачу, возвращаясьundefined
разные,Reflect.getOwnPropertyDescriptor(1, 'foo')
броситTypeError
ошибка - сReflect.defineProperty
Типа, ошибка дляtarget
брошена пустота. Вы также знаете, что мы можем использоватьReflect.getOwnPropertyDescriptor
заменятьObject.getOwnPropertyDescriptor
сейчас:
var myObject = {};
Object.defineProperty(myObject, 'hidden', {
value: true,
enumerable: false,
});
var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');
assert.deepEqual(theDescriptor, { value: true, enumerable: true });
// 老的风格
var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');
assert.deepEqual(theDescriptor, { value: true, enumerable: true });
assert(Object.getOwnPropertyDescriptor(1, 'foo') === undefined)
Reflect.getOwnPropertyDescriptor(1, 'foo'); // throws TypeError
Reflect.deleteProperty ( target, propertyKey )
очень очень интересно,Reflect.deleteProperty
Возможность удалить атрибут целевого объекта. До ES6 вы обычно проходилиdelete obj.foo
, теперь вы можете использоватьReflect.deleteProperty(obj, 'foo')
для удаления свойств объекта.Reflect.deleteProperty
Слегка многословный, семантически такой же, какdelete
Ключевое слово несколько разное, но для удаления объекта есть такая же роль. Оба называются встроеннымtarget[[Delete]](propertyKey)
Метод - ноdelete
Операция также может «работать» со ссылками, не относящимися к объектам (такими как переменные), поэтому она выполняет больше проверок переданных ей операндов и, возможно, выдает ошибку:
var myObj = { foo: 'bar' };
delete myObj.foo;
assert(myObj.hasOwnProperty('foo') === false);
myObj = { foo: 'bar' };
Reflect.deleteProperty(myObj, 'foo');
assert(myObj.hasOwnProperty('foo') === false);
Опять же, если вы хотите, вы можете использовать этот «новый способ» для удаления свойств. Этот метод, очевидно, предназначен для более явного использования, т. е. для удаления атрибута.
Reflect.getPrototypeOf ( target )
Замена/прекращение поддержки методов Object продолжается — на этот раз пришло времяObject.getPrototypeOf
. Как и его родственные методы, если вы передаете литерал, такой как Number и String ,null
илиundefined
это недействительноtarget
,Reflect.getPropertyOf
броситTypeError
ошибка, покаObject.getPropertyOf
принудительное преобразованиеtarget
для объекта - так'a'
сталObject('a')
. Помимо синтаксиса, они почти идентичны:
var myObj = new FancyThing();
assert(Reflect.getPrototypeOf(myObj) === FancyThing.prototype);
// 老的风格
assert(Object.getPrototypeOf(myObj) === FancyThing.prototype);
Object.getPrototypeOf(1); // undefined
Reflect.getPrototypeOf(1); // TypeError
Reflect.setPrototypeOf ( target, proto )
Конечно,getProtopertyOf
не может уйтиsetPropertyOf
. Сейчас,Object.setPrototypeOf
Будет выброшена ошибка для входящих аргументов, не являющихся объектами, но он попытается преобразовать входящий аргумент в Object, и если встроенный[[SetPrototype]]
Операция не удалась и выкинетTypeError
, и в случае успеха вернетtarget
параметр.Reflect.setPrototypeOf
Простые основы - если он получает аргумент, не являющийся объектом, он выдаетTypeError
ошибка, но кроме этого он возвращает[[SetPrototypeOf]]
Результат — это логическое значение, указывающее, была ли операция ошибкой. Это полезно, потому что вы можете напрямую сказать, если операция неверна, без необходимости использоватьtry
/catch
, который будет обнаруживать другие ошибки, вызванные ошибками передачи параметров.TypeErrors
.
var myObj = new FancyThing();
assert(Reflect.setPrototypeOf(myObj, OtherThing.prototype) === true);
assert(Reflect.getPrototypeOf(myObj) === OtherThing.prototype);
// 老的风格
assert(Object.setPrototypeOf(myObj, OtherThing.prototype) === myObj);
assert(Object.getPrototypeOf(myObj) === FancyThing.prototype);
Object.setPrototypeOf(1); // TypeError
Reflect.setPrototypeOf(1); // TypeError
var myFrozenObj = new FancyThing();
Object.freeze(myFrozenObj);
Object.setPrototypeOf(myFrozenObj); // TypeError
assert(Reflect.setPrototypeOf(myFrozenObj) === false);
Reflect.isExtensible (target)
Опять же, это заменаObject.isExtensible
Да, но это сложнее, чем последнее. До ES6 (скажем, ES5), если вы передали параметр, не являющийся объектом (typeof target !== object
),Object.isExtensible
броситTypeError
. ES6 изменился семантически (о боже! Он фактически изменил существующий API!), так что, когда передается параметр, не являющийся объектом,Object.isExtensible
вернутьfalse
- Потому что не-объекты действительно не расширяемы. Итак, в ES6 этот оператор, который ранее выдавал ошибку:Object.isExtensible(1) === false
Теперь ведет себя так, как и следовало ожидать, с более точной семантикой.
Ключевой момент приведенного выше краткого исторического обзора заключается в том, чтоReflect.isExtensible
Использует старое поведение выдачи ошибки при передаче необъектного параметра. Я не совсем уверен, почему он это делает, но это так. Так техническиReflect.isExtensible
измененныйObject.isExtensible
семантика, ноObject.isExtensible
Семантика также изменилась. Следующий код иллюстрирует это:
var myObject = {};
var myNonExtensibleObject = Object.preventExtensions({});
assert(Reflect.isExtensible(myObject) === true);
assert(Reflect.isExtensible(myNonExtensibleObject) === false);
Reflect.isExtensible(1); // 抛出 TypeError
Reflect.isExtensible(false); // 抛出 TypeError
// 使用 Object.isExtensible
assert(Object.isExtensible(myObject) === true);
assert(Object.isExtensible(myNonExtensibleObject) === false);
// ES5 Object.isExtensible 语义
Object.isExtensible(1); // 在老版本的浏览器下,会抛出 TypeError
Object.isExtensible(false); // 在老版本的浏览器下,会抛出 TypeError
// ES6 Object.isExtensible 语义
assert(Object.isExtensible(1) === false); // 只工作在新的浏览器
assert(Object.isExtensible(false) === false); // 只工作在新的浏览器
Reflect.preventExtensions ( target )
Это последний метод, который объекты отражения заимствуют у Object. это иReflect.isExtensible
Есть похожая история; ES5Object.preventExtensions
Раньше он выдавал ошибку для аргументов, не являющихся объектами, но теперь, в ES6, он возвращает переданное значение, аReflect.preventExtensions
Следует старому поведению ES5 по выдаче ошибок для аргументов, не являющихся объектами. Кроме того, в случае успешной операцииObject.preventExtensions
может выдать ошибку, ноReflect.preventExtension
Простое возвращение true или false позволяет изящно обрабатывать сценарии отказа:
var myObject = {};
var myObjectWhichCantPreventExtensions = magicalVoodooProxyCode({});
assert(Reflect.preventExtensions(myObject) === true);
assert(Reflect.preventExtensions(myObjectWhichCantPreventExtensions) === false);
Reflect.preventExtensions(1); // 抛出 TypeError
Reflect.preventExtensions(false); // 抛出 TypeError
// 使用 Object.preventExtensions
assert(Object.preventExtensions(myObject) === true);
Object.preventExtensions(myObjectWhichCantPreventExtensions); // throws TypeError
// ES5 Object.preventExtensions 语义
Object.preventExtensions(1); // 抛出 TypeError
Object.preventExtensions(false); // 抛出 TypeError
// ES6 Object.preventExtensions 语义
assert(Object.preventExtensions(1) === 1);
assert(Object.preventExtensions(false) === false);
Reflect.enumerate ( target )
Обновление: в ES2016 (он же ES7) это было удалено.
myObject[Symbol.iterator]()
это единственный способ перебрать ключ или значение объекта.
Наконец, будет представлен совершенно новый метод Reflect!Reflect.enumerate
подержанные и новыеSymbol.iterator
функции (обсуждаемые в предыдущей главе) имеют одинаковый синтаксис, обе используют скрытый, известный только движку JavaScript[[Enumerate]]
метод. другими словами,Reflect.enumerate
Единственная альтернатива простоmyObject[Symbol.iterator()]
, только последний может быть переопределен, а первый - нет. Пример использования следующий:
var myArray = [1, 2, 3];
myArray[Symbol.enumerate] = function () {
throw new Error('Nope!');
}
for (let item of myArray) { // error thrown: Nope!
}
for (let item of Reflect.enumerate(myArray)) {
// 1 then 2 then 3
}
Reflect.get ( target, propertyKey [ , receiver ])
Reflect.get
Тоже новый метод. Это очень простой метод, который эффективно вызываетtarget[propertyKey]
. еслиtarget
не является объектом, вызов функции вызовет ошибку - это полезно, потому что в настоящее время, если вы пишете1['foo']
Такой код, он просто молча возвращаетсяundefined
,а такжеReflect.get(1, 'foo')
броситTypeError
ошибка!Reflect.get
интересная часть в том, что егоreceiver
параметр, еслиtarget[propertyKey]
является функцией-получателем, которая используется как this функции, как показано в следующем примере:
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
}
assert(Reflect.get(myObject, 'foo') === 1);
assert(Reflect.get(myObject, 'bar') === 2);
assert(Reflect.get(myObject, 'baz') === 3);
assert(Reflect.get(myObject, 'baz', myObject) === 3);
var myReceiverObject = {
foo: 4,
bar: 4,
};
assert(Reflect.get(myObject, 'baz', myReceiverObject) === 8);
// 非对象将抛出错误
Reflect.get(1, 'foo'); // 抛出 TypeError
Reflect.get(false, 'foo'); // 抛出 TypeError
// 老的风格下,静默返回 `undefined`:
assert(1['foo'] === undefined);
assert(false['foo'] === undefined);
Reflect.set ( target, propertyKey, V [ , receiver ] )
Вы можете примерно догадаться, что делает этот метод. этоReflect.get
Родственный метод , который принимает еще один параметр — значение, которое необходимо установить. Такие какReflect.get
Такой же,Reflect.set
выдаст ошибку при передаче параметра, не являющегося объектом, а также будет иметьreceiver
спецификация параметраtarget[propertyKey]
используется для функций установкиthis
. Должен быть предыдущий пример кода:
var myObject = {
foo: 1,
set bar(value) {
return this.foo = value;
},
}
assert(myObject.foo === 1);
assert(Reflect.set(myObject, 'foo', 2));
assert(myObject.foo === 2);
assert(Reflect.set(myObject, 'bar', 3));
assert(myObject.foo === 3);
assert(Reflect.set(myObject, 'bar', myObject) === 4);
assert(myObject.foo === 4);
var myReceiverObject = {
foo: 0,
};
assert(Reflect.set(myObject, 'bar', 1, myReceiverObject));
assert(myObject.foo === 4);
assert(myReceiverObject.foo === 1);
// 非对象将抛出错误
Reflect.set(1, 'foo', {}); // 抛出 TypeError
Reflect.set(false, 'foo', {}); // 抛出 TypeError
// 老的风格下,静默返回 `undefined`:
1['foo'] = {};
false['foo'] = {};
assert(1['foo'] === undefined);
assert(false['foo'] === undefined);
Reflect.has ( target, propertyKey )
Reflect.has
это очень интересный метод, потому что он по существу такой же, какin
Операторы имеют ту же функциональность (вне циклов). Оба используют встроенный[[HasProperty]]
, и будет вtarget
Выдает ошибку, когда не является объектом. Если вы не предпочитаете стиль вызова функции, по сравнению сin
, мало использовалсяReflect.has
, но он имеет важные применения в других аспектах языка, которые будут разъяснены в следующей главе. В любом случае, давайте сначала посмотрим, как его использовать:
myObject = {
foo: 1,
};
Object.setPrototypeOf(myObject, {
get bar() {
return 2;
},
baz: 3,
});
// 不使用 Reflect.has:
assert(('foo' in myObject) === true);
assert(('bar' in myObject) === true);
assert(('baz' in myObject) === true);
assert(('bing' in myObject) === false);
// 使用 Reflect.has:
assert(Reflect.has(myObject, 'foo') === true);
assert(Reflect.has(myObject, 'bar') === true);
assert(Reflect.has(myObject, 'baz') === true);
assert(Reflect.has(myObject, 'bing') === false);
Reflect.ownKeys ( target )
Этот метод уже упоминался в этой статье, вы можете увидетьReflect.ownKeys
Достигнуто[[OwnPropertyKeys]]
, вы вспоминаете вышесказанное, вы знаете, что это связаноObject.getOwnPropertyNames
а такжеObject.getOwnPropertySymbols
результат. Это позволяетReflect.ownKeys
играет незаменимую роль. См. использование ниже:
var myObject = {
foo: 1,
bar: 2,
[Symbol.for('baz')]: 3,
[Symbol.for('bing')]: 4,
};
assert.deepEqual(Object.getOwnPropertyNames(myObject), ['foo', 'bar']);
assert.deepEqual(Object.getOwnPropertySymbols(myObject), [Symbol.for('baz'), Symbol.for('bing')]);
// 不使用 Reflect.ownKeys:
var keys = Object.getOwnPropertyNames(myObject).concat(Object.getOwnPropertySymbols(myObject));
assert.deepEqual(keys, ['foo', 'bar', Symbol.for('baz'), Symbol.for('bing')]);
// 使用 Reflect.ownKeys:
assert.deepEqual(Reflect.ownKeys(myObject), ['foo', 'bar', Symbol.for('baz'), Symbol.for('bing')]);
В заключение
У нас есть тщательное обсуждение каждого метода Reflect. Мы видели новые версии некоторых существующих методов, некоторые измененные, некоторые совершенно новые — выводя отражение JavaScript на новый уровень. Вы можете выбросить все это, если хотитеObject
.*/Function.*
метод, сReflect
Альтернатива, если вы не хотите, не волнуйтесь, не используйте его, ничего не изменится.
Я не хочу, чтобы вы читали это с пустыми руками и ничего не получили. если вы хотите использоватьReflect
, мы оказали вам поддержку — в рамках работы над этой статьей я представилпулл-реквест на eslint,существуетv1.0.0
Версия,ESlint имеет prefer-reflect
правило, который позволяет получать подсказки от ESLint при использовании старых версий методов Reflect. Вы также можете посмотреть на мойeslint-config-strictнастроить, включитьprefer-reflect
правил (также добавлено много дополнительных правил). Конечно, если вы решите использовать Reflect, вам может понадобиться полифилл; к счастью, есть несколько хороших доступных полифилов, таких какcore-jsа такжеharmony-reflect.
Что вы думаете о новом Reflect API? Планируете использовать его в своем проекте? Вы можете написать мне сообщение в моем Твиттере, я@keithamus.
И не забывайте, скоро выйдет третья часть этой серии, Прокси, и я не буду откладывать ее еще на два месяца. (Уже опубликовано:nuggets.capable/post/684490…
Наконец, спасибо@mttshwа также@WebReflectionВнимательное изучение моей работы сделало статью более качественной, чем ожидалось.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,React,внешний интерфейс,задняя часть,товар,дизайнЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.