ملاحظة مدير الجلسة: الرجاء مقاومة الرغبة في تعديل الرمز أو إزالة هذا الإشعار. قد يكون نمط المسافة البيضاء جزءًا من السؤال وبالتالي لا ينبغي العبث به بشكل غير ضروري. إذا كنت في معسكر "المسافة البيضاء غير ذات أهمية" ، فيجب أن تكون قادرًا على قبول الرمز كما هو.
هل يمكن على الإطلاق تقييم (a== 1 && a ==2 && a==3)
إلى true
في JavaScript؟
هذا هو سؤال المقابلة التي طرحتها شركة تكنولوجيا كبرى. لقد حدث ذلك منذ أسبوعين ، لكنني ما زلت أحاول العثور على الجواب. أعلم أننا لا نكتب أبداً مثل هذا الرمز في مهمتنا اليومية ، لكنني فضولي.
إذا استفدت من كيف يعمل ==
، فيمكنك ببساطة إنشاء كائن باستخدام دالة toString
(أو valueOf
) مخصصة تقوم بتغيير ما يتم إرجاعه في كل مرة يتم استخدامه بحيث تلبي جميع الشروط الثلاثة.
const a = {
i: 1,
toString: function () {
return a.i++;
}
}
if(a == 1 && a == 2 && a == 3) {
console.log('Hello World!');
}
سبب هذا يرجع إلى استخدام عامل المساواة فضفاضة. عند استخدام مساواة فضفاضة ، إذا كان أحد المعاملات من نوع مختلف عن الآخر ، فسيحاول المحرك تحويل واحد إلى الآخر. في حالة وجود كائن على اليسار ورقم على اليمين ، سيحاول تحويل الكائن إلى رقم عن طريق الاتصال أولاً بـ valueOf
إذا كان قابلاً للاستدعاء ، وإذا تعذر ذلك ، فسوف يتصل بـ toString
. لقد استخدمت toString
في هذه الحالة لمجرد أنه ما يتبادر إلى الذهن ، فإن valueOf
سيكون له معنى أكبر. إذا قمت بدلاً من ذلك بإرجاع سلسلة من toString
، فسيحاول المحرك حينئذٍ تحويل السلسلة إلى رقم يمنحنا النتيجة النهائية نفسها ، على الرغم من وجود مسار أطول قليلاً.
لم أستطع المقاومة - الإجابات الأخرى صحيحة بلا شك ، لكن لا يمكنك فعلاً تجاوز الشفرة التالية:
var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if(aᅠ==1 && a== 2 &&ᅠa==3) {
console.log("Why hello there!")
}
لاحظ التباعد الغريب في عبارة if
(التي نسختها من سؤالك). إنه Hangul بعرض نصف (هذا كوري لأولئك الذين ليسوا مألوفين) وهو حرف مساحة Unicode لا يتم تفسيره بواسطة برنامج ECMA كحرف مساحة - وهذا يعني أنه حرف صالح لمعرف. لذلك هناك ثلاثة متغيرات مختلفة تمامًا ، أحدها مع الهانغول بعد الأخر ، والآخر به من قبل والأخير بآخر. استبدال المسافة بـ _
لقابلية القراءة ، سيبدو نفس الرمز كما يلي:
var a_ = 1;
var a = 2;
var _a = 3;
if(a_==1 && a== 2 &&_a==3) {
console.log("Why hello there!")
}
تحقق من التحقق من صحة على Mathias 'متغير اسم المدقق . إذا تم تضمين هذا التباعد الغريب فعليًا في سؤالهم ، فأنا متأكد من أنه تلميح لهذا النوع من الإجابة.
لا تفعل هذا. بشكل جاد.
تحرير: لقد لاحظت أنه (على الرغم من عدم السماح ببدء تشغيل متغير) ، فإن نجار العرض الصفري و و- الصفر الصفري - غير نجار الأحرف مسموح بها أيضًا في أسماء متغير - انظر التعتيم جافا سكريبت مع الأحرف ذات العرض الصفري - إيجابيات وسلبيات؟ .
هذا سيبدو كما يلي:
var a= 1;
var a= 2; //one zero-width character
var a= 3; //two zero-width characters (or you can use the other one)
if(a==1&&a==2&&a==3) {
console.log("Why hello there!")
}
IT IS ممكن!
var i = 0;
with({
get a() {
return ++i;
}
}) {
if (a == 1 && a == 2 && a == 3)
console.log("wohoo");
}
هذا يستخدم getter داخل عبارة with
للسماح لـ a
بالتقييم إلى ثلاث قيم مختلفة.
... هذا لا يزال لا يعني أن هذا ينبغي أن تستخدم في رمز حقيقي ...
والأسوأ من ذلك ، ستعمل هذه الخدعة أيضًا مع استخدام ===
.
var i = 0;
with({
get a() {
return ++i;
}
}) {
if (a !== a)
console.log("yep, this is printed.");
}
مثال بدون حروف أو valueOf:
a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3);
يعمل هذا لأن ==
تستدعي toString
الذي يستدعي .join
للصفائف.
حل آخر ، باستخدام Symbol.toPrimitive
وهو مكافئ ES6 لـ toString/valueOf
:
let i = 0;
let a = { [Symbol.toPrimitive]: () => ++i };
console.log(a == 1 && a == 2 && a == 3);
إذا تم سؤالك عما إذا كان من الممكن (لا يجب) ، فيمكنه أن يطلب من "a" إرجاع رقم عشوائي. سيكون صحيحًا إذا كان يولد 1 و 2 و 3 بالتتابع.
with({
get a() {
return Math.floor(Math.random()*4);
}
}){
for(var i=0;i<1000;i++){
if (a == 1 && a == 2 && a == 3){
console.log("after " + (i+1) + " trials, it becomes true finally!!!");
break;
}
}
}
عندما لا تستطيع فعل أي شيء دون تعبيرات منتظمة:
var a = {
r: /\d/g,
valueOf: function(){
return this.r.exec(123)[0]
}
}
if (a == 1 && a == 2 && a == 3) {
console.log("!")
}
إنه يعمل بسبب custom valueOf
الأسلوب الذي يتم استدعاؤه عند مقارنة الكائن مع البدائي (مثل Number). الخدعة الرئيسية هي أن a.valueOf
تُرجع قيمة جديدة في كل مرة لأنها تستدعي exec
في تعبير منتظم بعلامة g
، والتي تسبب تحديث lastIndex
من هذا التعبير العادي في كل مرة يتم العثور على تطابق منتظم. لذلك في المرة الأولى this.r.lastIndex == 0
، تتطابق مع 1
وتحديثات lastIndex
: this.r.lastIndex == 1
، لذلك في المرة القادمة سوف تتطابق regex مع 2
وهكذا.
ويمكن تحقيق ذلك باستخدام ما يلي في النطاق العالمي. من أجل nodejs
، استخدم global
بدلاً من window
في الكود أدناه.
var val = 0;
Object.defineProperty(window, 'a', {
get: function() {
return ++val;
}
});
if (a == 1 && a == 2 && a == 3) {
console.log('yay');
}
تفسد هذه الإجابة المتغيرات الضمنية التي يوفرها النطاق العام في سياق التنفيذ عن طريق تحديد getter لاسترداد المتغير.
هذا ممكن في حالة المتغير a
الذي يتم الوصول إليه بواسطة ، قل 2 من عمال الويب من خلال SharedArrayBuffer بالإضافة إلى بعض النصوص الرئيسية. الاحتمال ضئيل ، لكن من المحتمل أنه عند ترجمة الشفرة إلى رمز الجهاز ، يقوم موظفو الويب بتحديث المتغير a
في الوقت المناسب تمامًا بحيث تكون الشروط a==1
و a==2
و a==3
راضية.
يمكن أن يكون هذا مثالًا على حالة السباق في بيئة متعددة الخيوط يوفرها عمال الويب و SharedArrayBuffer في JavaScript.
هنا هو التنفيذ الأساسي لما ورد أعلاه:
main.js
// Main Thread
const worker = new Worker('worker.js')
const modifiers = [new Worker('modifier.js'), new Worker('modifier.js')] // Let's use 2 workers
const sab = new SharedArrayBuffer(1)
modifiers.forEach(m => m.postMessage(sab))
worker.postMessage(sab)
worker.js
let array
Object.defineProperty(self, 'a', {
get() {
return array[0]
}
});
addEventListener('message', ({data}) => {
array = new Uint8Array(data)
let count = 0
do {
var res = a == 1 && a == 2 && a == 3
++count
} while(res == false) // just for clarity. !res is fine
console.log(`It happened after ${count} iterations`)
console.log('You should\'ve never seen this')
})
modifier.js
addEventListener('message' , ({data}) => {
setInterval( () => {
new Uint8Array(data)[0] = Math.floor(Math.random()*3) + 1
})
})
على جهاز MacBook Air ، يحدث بعد حوالي 10 مليارات تكرار في المحاولة الأولى:
محاولة ثانية:
كما قلت ، ستكون الفرص منخفضة ، ولكن بالنظر إلى ما يكفي من الوقت ، فسوف تصل إلى الحالة.
نصيحة: إذا استغرق الأمر وقتًا طويلاً في نظامك. حاول فقط a == 1 && a == 2
وتغيير Math.random()*3
إلى Math.random()*2
. إضافة المزيد والمزيد إلى قائمة يسقط فرصة ضرب.
هذا ممكن أيضًا باستخدام سلسلة من حروف الكتابة الذاتية:
(هذا مشابه لحل jontro ، ولكنه لا يتطلب متغير عداد.)
(() => {
"use strict";
Object.defineProperty(this, "a", {
"get": () => {
Object.defineProperty(this, "a", {
"get": () => {
Object.defineProperty(this, "a", {
"get": () => {
return 3;
}
});
return 2;
},
configurable: true
});
return 1;
},
configurable: true
});
if (a == 1 && a == 2 && a == 3) {
document.body.append("Yes, it’s possible.");
}
})();
لا أرى هذه الإجابة منشورة بالفعل ، لذلك سألقي هذا الرد في المزيج أيضًا. هذا يشبه جيف جيف مع مساحة الهانغول نصف العرض.
var a = 1;
var a = 2;
var а = 3;
if(a == 1 && a == 2 && а == 3) {
console.log("Why hello there!")
}
قد تلاحظ تباينًا بسيطًا مع الثاني ، لكن الأول والثالث مماثلان للعين المجردة. جميع الشخصيات الثلاثة مميزة:
a
- الحالة اللاتينية الصغيرة Aa
- عرض بالحروف اللاتينية بالأحرف الكاملة Full Aа
- الحالة السيريلية الصغيرة A
المصطلح العام لذلك هو "الأشكال المتجانسة": أحرف يونيكود مختلفة تبدو متشابهة. عادة يصعب الحصول على ثلاثة لا يمكن تمييزها تمامًا ، لكن في بعض الحالات ، يمكنك الحظ. ستعمل A و Α و А و better بشكل أفضل (Latin-A و Greek Alpha و Cyrillic-A و و Cherokee-A على التوالي ؛ لسوء الحظ ، الأحرف الصغيرة اليونانية وشروكي تختلف كثيرا عن اللاتينية a
: α
، ꭺ
، وبالتالي لا تساعد في المقتطف أعلاه).
هناك فئة كاملة من هجمات Homoglyph هناك ، والأكثر شيوعًا في أسماء النطاقات المزيفة (على سبيل المثال. wikipediа.org
(السيريلية) مقابل wikipedia.org
(اللاتينية)) ، لكن يمكن أن تظهر في الكود أيضًا ؛ يشار إليها عادة بأنها مخادعة (كما هو مذكور في تعليق ، [مخادعة] الأسئلة أصبحت الآن خارج الموضوع على PPCG ، ولكنها اعتادت أن تكون نوعًا من التحدي حيث تظهر هذه الأنواع من الأشياء فوق). لقد استخدمت هذا الموقع للعثور على الأشكال المتجانسة المستخدمة في هذه الإجابة.
بدلاً من ذلك ، يمكنك استخدام فئة لها ومثيل للفحص.
function A() {
var value = 0;
this.valueOf = function () { return ++value; };
}
var a = new A;
if (a == 1 && a == 2 && a == 3) {
console.log('bingo!');
}
تصحيح
باستخدام فئات ES6 سيبدو كهذا
class A {
constructor() {
this.value = 0;
this.valueOf();
}
valueOf() {
return this.value++;
};
}
let a = new A;
if (a == 1 && a == 2 && a == 3) {
console.log('bingo!');
}
if=()=>!0;
var a = 9;
if(a==1 && a== 2 && a==3)
{
document.write("<h1>Yes, it is possible!????</h1>")
}
الكود أعلاه هو نسخة قصيرة (بفضلForivin لملاحظاته في التعليقات) والرمز التالي أصلي:
var a = 9;
if(a==1 && a== 2 && a==3)
{
//console.log("Yes, it is possible!????")
document.write("<h1>Yes, it is possible!????</h1>")
}
//--------------------------------------------
function if(){return true;}
إذا رأيت فقط الجانب العلوي من الكود وقمت بتشغيله ، فأنت تقول WOW ، كيف؟
لذلك أعتقد أنه يكفي قول {نعم ، من الممكن} لشخص قال لك: {لا شيء مستحيل}
الخدعة: استخدمت حرفًا مخفيًا بعد
if
لإنشاء دالة يشبه اسمهاif
. في JavaScript ، لا يمكننا تجاوز الكلمات الرئيسية ، لذلك اضطررت إلى استخدام هذه الطريقة. إنهاif
وهمية ، لكنها تعمل من أجلك في هذه الحالة!
كما كتبت إصدار C # (مع زيادة قيمة خاصية تكنيك):
static int _a;
public static int a => ++_a;
public static void Main()
{
if(a==1 && a==2 && a==3)
{
Console.WriteLine("Yes, it is possible!????");
}
}
في JavaScript ، لا توجد أعداد صحيحة ولكن Number
s فقط ، والتي يتم تنفيذها كأرقام فاصلة عائمة مزدوجة الدقة.
هذا يعني أنه إذا كان الرقم a
كبيرًا بما يكفي ، فيمكن اعتباره مساوياً لثلاثة أعداد صحيحة متتالية:
a = 100000000000000000
if (a == a+1 && a == a+2 && a == a+3){
console.log("Precision loss!");
}
صحيح ، هذا ليس بالضبط ما طلبه القائم بإجراء المقابلة (لا يعمل مع a=0
) ، لكنه لا ينطوي على أي خدعة ذات وظائف خفية أو التحميل الزائد للمشغل.
كمرجع ، توجد حلول a==1 && a==2 && a==3
في Ruby و Python. مع تعديل بسيط ، من الممكن أيضًا في Java.
مع ==
مخصص:
class A
def ==(o)
true
end
end
a = A.new
if a == 1 && a == 2 && a == 3
puts "Don't do this!"
end
أو a
متزايد:
def a
@a ||= 0
@a += 1
end
if a == 1 && a == 2 && a == 3
puts "Don't do this!"
end
class A:
def __eq__(self, who_cares):
return True
a = A()
if a == 1 and a == 2 and a == 3:
print("Don't do that!")
من الممكن تعديل ذاكرة التخزين المؤقت Java Integer
:
package stackoverflow;
import Java.lang.reflect.Field;
public class IntegerMess
{
public static void main(String[] args) throws Exception {
Field valueField = Integer.class.getDeclaredField("value");
valueField.setAccessible(true);
valueField.setInt(1, valueField.getInt(42));
valueField.setInt(2, valueField.getInt(42));
valueField.setInt(3, valueField.getInt(42));
valueField.setAccessible(false);
Integer a = 42;
if (a.equals(1) && a.equals(2) && a.equals(3)) {
System.out.println("Bad idea.");
}
}
}
هذه نسخة مقلوبة من @ Jeff's answer * حيث يتم استخدام حرف مخفي (U + 115F أو U + 1160 أو U + 3164) لإنشاء متغيرات تبدو مثل 1
و 2
و 3
.
var a = 1;
var ᅠ1 = a;
var ᅠ2 = a;
var ᅠ3 = a;
console.log( a ==ᅠ1 && a ==ᅠ2 && a ==ᅠ3 );
* يمكن تبسيط هذه الإجابة عن طريق استخدام غير صفار عرض الصفر (U + 200C) ونجار عرض الصفر (U + 200D). يُسمح لكل من هذه الأحرف داخل المعرفات ولكن ليس في البداية:
var a = 1;
var a = 2;
var a = 3;
console.log(a == 1 && a == 2 && a == 3);
/****
var a = 1;
var a\u200c = 2;
var a\u200d = 3;
console.log(a == 1 && a\u200c == 2 && a\u200d == 3);
****/
الحيل الأخرى ممكنة باستخدام نفس الفكرة ، على سبيل المثال باستخدام محددات أشكال Unicode لإنشاء متغيرات تبدو متشابهة تمامًا (a︀ = 1; a︁ = 2; a︀ == 1 && a︁ == 2; // true
).
القاعدة رقم واحد من المقابلات ؛ لا تقل أبدا المستحيل.
لا حاجة للخداع شخصية خفية.
window.__defineGetter__( 'a', function(){
if( typeof i !== 'number' ){
// define i in the global namespace so that it's not lost after this function runs
i = 0;
}
return ++i;
});
if( a == 1 && a == 2 && a == 3 ){
alert( 'Oh dear, what have we done?' );
}
بصراحة ، ما إذا كانت هناك طريقة لتقويمها إلى صواب أم لا (وكما أوضح الآخرون ، هناك طرق متعددة) ، فإن الإجابة التي كنت أبحث عنها ، متحدثًا كشخص أجرى مئات المقابلات ، ستكون شيء على غرار:
"حسنًا ، ربما نعم في ظل مجموعة غريبة من الظروف التي لم تكن واضحة لي على الفور ... ولكن إذا واجهت هذا في الكود الحقيقي فسأستخدم تقنيات تصحيح الأخطاء الشائعة لمعرفة كيف ولماذا كانت تفعل ما كانت تفعله ثم قم بإعادة تفعيل الكود فورًا لتجنب هذا الموقف ... ولكن الأهم من ذلك: أود ألا أكتب مطلقًا هذا الكود في المقام الأول لأن هذا هو تعريف الكود المعقد ، وأنا لا أسعى أبدًا إلى كتابة الكود المعقد ".
أظن أن بعض المقابلات سوف يجرؤون على امتلاك ما هو واضح ليكون سؤالًا صعبًا للغاية ، لكنني لا أمانع المطورين الذين لديهم رأي ، خاصةً عندما يتمكنون من عمل نسخة احتياطية من التفكير المنطقي ويمكنهم دمج سؤالي في بيان هادف عن أنفسهم.
إليك صيغة أخرى ، باستخدام صفيف لإظهار القيم التي تريدها.
const a = {
n: [3,2,1],
toString: function () {
return a.n.pop();
}
}
if(a == 1 && a == 2 && a == 3) {
console.log('Yes');
}
حسنا ، الاختراق آخر مع المولدات:
const value = function* () {
let i = 0;
while(true) yield ++i;
}();
Object.defineProperty(this, 'a', {
get() {
return value.next().value;
}
});
if (a === 1 && a === 2 && a === 3) {
console.log('yo!');
}
في الواقع ، الإجابة على الجزء الأول من السؤال هي "نعم" في كل لغة برمجة. على سبيل المثال ، هذا في حالة C/C++:
#define a (b++)
int b = 1;
if (a ==1 && a== 2 && a==3) {
std::cout << "Yes, it's possible!" << std::endl;
} else {
std::cout << "it's impossible!" << std::endl;
}
باستخدام الوكلاء :
var a = new Proxy({ i: 0 }, {
get: (target, name) => name === Symbol.toPrimitive ? () => ++target.i : target[name],
});
console.log(a == 1 && a == 2 && a == 3);
يتظاهر الوكلاء أساسًا بأنهم كائن مستهدف (المعلمة الأولى) ، لكنهم يعترضون العمليات على الكائن الهدف (في هذه الحالة عملية "الحصول على خاصية") بحيث توجد فرصة للقيام بشيء آخر بخلاف سلوك الكائن الافتراضي. في هذه الحالة ، يتم استدعاء إجراء "الحصول على خاصية" على a
عندما يختلط ==
بنوعه لمقارنته بكل رقم. هذا يحدث:
{ i: 0 }
، حيث الخاصية i
هي عدادناa
a ==
، يتم إجبار نوع a
إلى قيمة أوليةa[Symbol.toPrimitive]()
داخليًاa[Symbol.toPrimitive]
باستخدام "get getler"Symbol.toPrimitive
، وفي هذه الحالة تزيد وتعيد العداد من الكائن الهدف: ++target.i
. إذا تم استرداد خاصية مختلفة ، فسنعود إلى إرجاع قيمة الخاصية الافتراضية ، target[name]
وبالتالي:
var a = ...; // a.valueOf == target.i == 0
a == 1 && // a == ++target.i == 1
a == 2 && // a == ++target.i == 2
a == 3 // a == ++target.i == 3
كما هو الحال مع معظم الإجابات الأخرى ، يعمل هذا فقط مع فحص فضفاض للمساواة (==
) ، لأن اختبارات المساواة الصارمة (===
) لا تقم بالإكراه على النوع الذي يمكن للوكيل اعتراضه.
نفسه ، ولكن مختلف ، ولكن لا يزال نفسه (يمكن "اختباره" عدة مرات):
const a = { valueOf: () => this.n = (this.n || 0) % 3 + 1}
if(a == 1 && a == 2 && a == 3) {
console.log('Hello World!');
}
if(a == 1 && a == 2 && a == 3) {
console.log('Hello World!');
}
بدأت فكرتي من كيفية عمل معادلة نوع كائن رقم.
إجابة ECMAScript 6 التي تستخدم الرموز:
const a = {value: 1};
a[Symbol.toPrimitive] = function() { return this.value++ };
console.log((a == 1 && a == 2 && a == 3));
نظرًا لاستخدام ==
، من المفترض أن يقوم JavaScript بإكراه a
إلى شيء قريب من المعامل الثاني (1
، 2
، 3
في هذه الحالة). ولكن قبل أن يحاول JavaScript تحديد الإكراه من تلقاء نفسه ، فإنه يحاول الاتصال Symbol.toPrimitive
. إذا قدمت Symbol.toPrimitive
، فسيستخدم JavaScript القيمة التي تُرجعها وظيفتك. إذا لم يكن الأمر كذلك ، فستتصل JavaScript valueOf
.
أعتقد أن هذا هو الحد الأدنى من التعليمات البرمجية لتنفيذه:
i=0,a={valueOf:()=>++i}
if (a == 1 && a == 2 && a == 3) {
console.log('Mind === Blown');
}
إنشاء كائن وهمية باستخدام valueOf
الذي يزيد متغير عمومي i
على كل مكالمة. 23 حرفا!
هذا واحد يستخدم identProperty مع آثار جانبية لطيفة تسبب متغير عمومي!
var _a = 1
Object.defineProperty(this, "a", {
"get": () => {
return _a++;
},
configurable: true
});
console.log(a)
console.log(a)
console.log(a)
من خلال تجاوز valueOf
في إعلان فئة ، يمكن القيام بذلك:
class Thing {
constructor() {
this.value = 1;
}
valueOf() {
return this.value++;
}
}
const a = new Thing();
if(a == 1 && a == 2 && a == 3) {
console.log(a);
}
ما يحدث هو أن valueOf
يسمى في كل عامل مقارنة. في الأولى ، a
تساوي 1
، في الثانية ، a
تساوي 2
، وهكذا دواليك ، وهكذا ، لأنه يتم استدعاء valueOf
، يتم زيادة قيمة a
.
وبالتالي ، فإن console.log ستطلق وتخرج (في طرفيه على أي حال) Thing: { value: 4}
، مما يشير إلى أن الشرطية كانت صحيحة.