Есть ли в javascript оператор с нулевой связью (Elvis) или безопасный навигатор?
Я объясню на примере:
Оператор Элвиса (?:)
"Оператор Элвиса" - это сокращение тройного оператора Java. Один пример того, где это удобно, возврат значения "разумного значения по умолчанию" если выражение разрешает false или ноль. Простой пример может выглядеть так: это:
def gender = user.male ? "male" : "female" //traditional ternary operator usage
def displayName = user.name ?: "Anonymous" //more compact Elvis operator
Оператор безопасной навигации (?.)
Используется оператор безопасной навигации чтобы избежать исключения NullPointerException. Обычно, когда у вас есть ссылка на объект, который, возможно, потребуется проверить что он не имеет значения null до доступа методов или свойств объекта. Чтобы избежать этого, безопасная навигация оператор просто вернет null вместо того, чтобы бросать исключение, например так:
def user = User.find( "admin" ) //this might be null if 'admin' does not exist
def streetName = user?.address?.street //streetName will be null if user or user.address is null - no NPE thrown
Ответы
Ответ 1
Вместо оператора Элвиса вы можете использовать логический оператор "OR":
Например displayname = user.name || "Anonymous"
.
Но Javascript в настоящее время не имеет других функций. Я бы рекомендовал посмотреть CoffeeScript, если вам нужен альтернативный синтаксис. Он имеет некоторую стенографию, похожую на то, что вы ищете.
Например, Экзистенциальный оператор
zip = lottery.drawWinner?().address?.zipcode
Горячие клавиши
()-> // equivalent to function(){}
вызов сексуальной функции
func 'arg1','arg2' // equivalent to func('arg1','arg2')
Есть также многострочные комментарии и классы. Очевидно, вы должны скомпилировать это на javascript или вставить на страницу как <script type='text/coffeescript>'
, но он добавляет много функциональности:). Использование <script type='text/coffeescript'>
действительно предназначено только для разработки, а не для производства.
Ответ 2
Я думаю, что следующее эквивалентно оператору безопасной навигации, хотя и немного дольше:
var streetName = user && user.address && user.address.street;
streetName
тогда будет либо значением user.address.street
или undefined
.
Если вы хотите, чтобы по умолчанию он был чем-то другим, вы можете использовать комбинацию клавиш выше или дать:
var streetName = (user && user.address && user.address.street) || "Unknown Street";
Ответ 3
Javascript логический оператор OR короткое замыкание и может заменить вашего оператора "Элвис":
var displayName = user.name || "Anonymous";
Однако, насколько мне известно, нет эквивалента вашему оператору ?.
.
Ответ 4
Иногда я находил следующую идиому полезной:
a?.b?.c
можно переписать как:
((a||{}).b||{}).c
Это использует тот факт, что получение неизвестных атрибутов объекта возвращает неопределенное значение, а не генерирует исключение, как это происходит в null
или undefined
, поэтому мы заменяем пустое и неопределенное значение перед навигацией.
Ответ 5
Я думаю, что lodash _.get()
может помочь здесь, как и в _.get(user, 'name')
, и более сложные задачи, такие как _.get(o, 'a[0].b.c', 'default-value')
Ответ 6
Еще нет. Может быть, скоро. В настоящее время существует черновик спецификации:
https://github.com/tc39/proposal-optional-chaining
https://tc39.github.io/proposal-optional-chaining/
Однако сейчас мне нравится использовать lodash get(object, path [,defaultValue])
или dlv delve(obj, keypath)
Ответ 7
Для первого вы можете использовать ||
. Оператор Javascript "логический или", а не просто возвращающий законченные истинные и ложные значения, следует правилу возврата его левого аргумента, если оно истинно, и в противном случае оценивает и возвращает свой правый аргумент. Когда вас интересует только значение истины, оно работает одинаково, но это также означает, что foo || bar || baz
возвращает самый левый из foo, bar или baz, который содержит истинное значение.
Вы не найдете тот, который может отличить false от null, но 0, а пустая строка - это ложные значения, поэтому избегайте использования конструкции value || default
, где value
может быть законным 0 или ""
.
Ответ 8
Это более широко известно как оператор с нулевым коалесцированием. У Javascript нет этого.
Ответ 9
Здесь простой оператор elvis:
function elvis(object, path) {
return path ? path.split('.').reduce(function (nestedObject, key) {
return nestedObject && nestedObject[key];
}, object) : object;
}
> var o = { a: { b: 2 }, c: 3 };
> elvis(o)
{ a: { b: 2 }, c: 3 }
> elvis(o, 'a');
{ b: 2 }
> elvis(o, 'a.b');
2
> elvis(o, 'x');
undefined
Ответ 10
Вы можете добиться примерно того же эффекта, сказав:
var displayName = user.name || "Anonymous";
Ответ 11
У меня есть решение для этого, адаптируйте его к вашим собственным потребностям, выдержку из одной из моих библиотек:
elvisStructureSeparator: '.',
// An Elvis operator replacement. See:
// http://coffeescript.org/ --> The Existential Operator
// http://fantom.org/doc/docLang/Expressions.html#safeInvoke
//
// The fn parameter has a SPECIAL SYNTAX. E.g.
// some.structure['with a selector like this'].value transforms to
// 'some.structure.with a selector like this.value' as an fn parameter.
//
// Configurable with tulebox.elvisStructureSeparator.
//
// Usage examples:
// tulebox.elvis(scope, 'arbitrary.path.to.a.function', fnParamA, fnParamB, fnParamC);
// tulebox.elvis(this, 'currentNode.favicon.filename');
elvis: function (scope, fn) {
tulebox.dbg('tulebox.elvis(' + scope + ', ' + fn + ', args...)');
var implicitMsg = '....implicit value: undefined ';
if (arguments.length < 2) {
tulebox.dbg(implicitMsg + '(1)');
return undefined;
}
// prepare args
var args = [].slice.call(arguments, 2);
if (scope === null || fn === null || scope === undefined || fn === undefined
|| typeof fn !== 'string') {
tulebox.dbg(implicitMsg + '(2)');
return undefined;
}
// check levels
var levels = fn.split(tulebox.elvisStructureSeparator);
if (levels.length < 1) {
tulebox.dbg(implicitMsg + '(3)');
return undefined;
}
var lastLevel = scope;
for (var i = 0; i < levels.length; i++) {
if (lastLevel[levels[i]] === undefined) {
tulebox.dbg(implicitMsg + '(4)');
return undefined;
}
lastLevel = lastLevel[levels[i]];
}
// real return value
if (typeof lastLevel === 'function') {
var ret = lastLevel.apply(scope, args);
tulebox.dbg('....function value: ' + ret);
return ret;
} else {
tulebox.dbg('....direct value: ' + lastLevel);
return lastLevel;
}
},
работает как шарм. Наслаждайтесь меньшей болью!
Ответ 12
Вы можете свернуть свой собственный:
function resolve(objectToGetValueFrom, stringOfDotSeparatedParameters) {
var returnObject = objectToGetValueFrom,
parameters = stringOfDotSeparatedParameters.split('.'),
i,
parameter;
for (i = 0; i < parameters.length; i++) {
parameter = parameters[i];
returnObject = returnObject[parameter];
if (returnObject === undefined) {
break;
}
}
return returnObject;
};
И используйте это так:
var result = resolve(obj, 'a.b.c.d');
* результат не определен, если какой-либо из a, b, c или d не определен.
Ответ 13
Да, есть! 🍾
foo?.bar
наконец-то работает в JS!
Вы должны установить @babel/plugin-proposal-optional-chaining
для этого.
Вот мои настройки, которые позволяют это:
// package.json
{
"name": "optional-chaining-test",
"version": "1.0.0",
"main": "index.js",
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "7.2.0",
"@babel/core": "7.2.0",
"@babel/preset-env": "^7.5.5"
}
...
}
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"debug": true
}
]
],
"plugins": [
"@babel/plugin-proposal-optional-chaining"
]
}
// index.js
const foo = { bar: "hello" }
console.log(foo?.bar); // it works
Ответ 14
Я прочитал эту статью (https://www.beyondjava.net/elvis-operator-aka-safe-navigation-javascript-typescript) и изменил решение с помощью прокси.
function safe(obj) {
return new Proxy(obj, {
get: function(target, name) {
const result = target[name];
if (!!result) {
return (result instanceof Object)? safe(result) : result;
}
return safe.nullObj;
},
});
}
safe.nullObj = safe({});
safe.safeGet= function(obj, expression) {
let safeObj = safe(obj);
let safeResult = expression(safeObj);
if (safeResult === safe.nullObj) {
return undefined;
}
return safeResult;
}
Вы называете это так:
safe.safeGet(example, (x) => x.foo.woo)
Результат будет неопределенным для выражения, которое встречает ноль или неопределенное на своем пути. Вы можете сойти с ума и изменить прототип Object!
Object.prototype.getSafe = function (expression) {
return safe.safeGet(this, expression);
};
example.getSafe((x) => x.foo.woo);
Ответ 15
Перепрыгнув очень поздно, появилось предложение [1] об опциональной цепочке в настоящее время на стадии 2, с доступным плагином babel [2]. Это не в настоящее время ни в одном браузере, о котором я знаю.
- https://github.com/tc39/proposal-optional-chaining
- https://www.npmjs.com/package/@babel/plugin-proposal-optional-chaining
Ответ 16
Это было проблемой для меня долгое время. Мне нужно было найти решение, которое можно легко перенести, как только мы получим оператора Элвиса или что-то в этом роде.
Это то, что я использую; работает как для массивов, так и для объектов
поместите это в файл tools.js или что-то в этом роде
// this will create the object/array if null
Object.prototype.__ = function (prop) {
if (this[prop] === undefined)
this[prop] = typeof prop == 'number' ? [] : {}
return this[prop]
};
// this will just check if object/array is null
Object.prototype._ = function (prop) {
return this[prop] === undefined ? {} : this[prop]
};
пример использования:
let student = {
classes: [
'math',
'whatev'
],
scores: {
math: 9,
whatev: 20
},
loans: [
200,
{ 'hey': 'sup' },
500,
300,
8000,
3000000
]
}
// use one underscore to test
console.log(student._('classes')._(0)) // math
console.log(student._('classes')._(3)) // {}
console.log(student._('sports')._(3)._('injuries')) // {}
console.log(student._('scores')._('whatev')) // 20
console.log(student._('blabla')._('whatev')) // {}
console.log(student._('loans')._(2)) // 500
console.log(student._('loans')._(1)._('hey')) // sup
console.log(student._('loans')._(6)._('hey')) // {}
// use two underscores to create if null
student.__('loans').__(6)['test'] = 'whatev'
console.log(student.__('loans').__(6).__('test')) // whatev
хорошо, я знаю, что это делает код немного нечитаемым, но это простое решение с одним вкладышем и отлично работает. Надеюсь, это кому-нибудь поможет :)
Ответ 17
Это было интересное решение для безопасного навигатора с использованием некоторого mixin.
http://jsfiddle.net/avernet/npcmv/
// Assume you have the following data structure
var companies = {
orbeon: {
cfo: "Erik",
cto: "Alex"
}
};
// Extend Underscore.js
_.mixin({
// Safe navigation
attr: function(obj, name) { return obj == null ? obj : obj[name]; },
// So we can chain console.log
log: function(obj) { console.log(obj); }
});
// Shortcut, 'cause I'm lazy
var C = _(companies).chain();
// Simple case: returns Erik
C.attr("orbeon").attr("cfo").log();
// Simple case too, no CEO in Orbeon, returns undefined
C.attr("orbeon").attr("ceo").log();
// IBM unknown, but doesn't lead to an error, returns undefined
C.attr("ibm").attr("ceo").log();
Ответ 18
ОБНОВЛЕНИЕ СЕНТЯБРЬ 2019
Да, JS теперь поддерживает это.
Опциональная цепочка скоро появится в v8 подробнее
Ответ 19
Лично я использую
function e(e,expr){try{return eval(expr);}catch(e){return null;}};
и, например, безопасный get:
var a = e(obj,'e.x.y.z.searchedField');