แปลงสตริงเป็นสตริงแม่แบบ


147

เป็นไปได้ไหมที่จะสร้างเทมเพลตสตริงเป็นสตริงปกติ

let a="b:${b}";

แล้วแปลงเป็นสตริงแม่แบบ

let b=10;
console.log(a.template());//b:10

โดยไม่ต้องeval, new Functionและวิธีการอื่น ๆ ของการสร้างรหัสแบบไดนามิก?


5
คุณหาวิธีที่จะบรรลุเป้าหมายนี้หรือไม่? ฉันอาจต้องทำวันหนึ่งและอยากรู้ว่าคุณมาถึงที่ใด
Bryan Rayner

@BryanRayner สมมติว่าโปรแกรม js ของคุณพยายามดึงข้อมูลจาก API ที่เหลือซึ่ง url อยู่ในไฟล์ config.js เป็นสตริง "/ resources / <resource_id> / update /" และคุณใส่ "resource_id" แบบไดนามิกจากโปรแกรมของคุณ . ถ้าคุณไม่ต้องการแยก URL นั้นออกเป็นส่วน ๆ และบันทึกในพื้นที่ต่าง ๆ คุณต้องมีการประมวลผลเทมเพลตสตริง
Ryu_hayabusa


แทนที่จะใช้ eval ที่ดีกว่านั้นใช้ regex Eval ไม่แนะนำ & ท้อแท้อย่างมากดังนั้นโปรดอย่าใช้มัน developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ...... ! ให้ b = 10; ให้ a = "b: $ {b}"; ให้การตอบสนอง = a.replace (/ \ $ {\ w +} /, b); conssole.log (การตอบสนอง);
Vijay Palaskar

คำตอบ:


79

เนื่องจากสตริงแม่แบบของคุณต้องได้รับการอ้างอิงถึงbตัวแปรแบบไดนามิก (ในรันไทม์) ดังนั้นคำตอบคือ: ไม่เป็นไปไม่ได้หากไม่มีการสร้างรหัสแบบไดนามิก

แต่ด้วยevalความเรียบง่าย:

let tpl = eval('`'+a+'`');

7
EVAL ไม่ปลอดภัยจึงเป็นวิธีการอื่นในการสร้างรหัสแบบไดนามิก
KOLANICH

8
โดยเฉพาะอย่างยิ่งสำหรับ @KOLANICH ว่ากรณี - คำพูดหลบหนีกลับมาอยู่ในสตริงและมันจะมีมากน้อยไม่ปลอดภัย:a let tpl = eval('`'+a.replace(/`/g,'\\`')+'`');ฉันคิดว่าสำคัญกว่านั้นคือการevalป้องกันคอมไพเลอร์เพื่อเพิ่มประสิทธิภาพรหัสของคุณ แต่ฉันคิดว่ามันไม่เกี่ยวข้องกับคำถามนี้
alexpods

3
ในความเป็นจริงคุณสามารถเรียกใช้ฟังก์ชันภายในสตริงแม่แบบ
KOLANICH

9
@KOLANICH evalขออภัยที่คุณไม่ชอบ evalแต่จำไว้ว่าแม่แบบตัวอักษรเป็นตัวเองรูปแบบของ ตัวอย่างสองตัวอย่าง: var test = Result: ${alert('hello')}; การทดสอบ var = Result: ${b=4}; ทั้งสองจะสิ้นสุดการดำเนินการรหัสโดยพลการในบริบทของสคริปต์ หากคุณต้องการอนุญาตสตริงที่กำหนดเองคุณอาจอนุญาตได้เช่นevalกัน
Manngo

6
ระวัง. เนื่องจากบางอย่างเช่น babel จะไม่ transpile นี้รหัสนี้จะไม่ทำงานใน IE
cgsd

79

ในโครงการของฉันฉันได้สร้างสิ่งนี้ด้วย ES6:

String.prototype.interpolate = function(params) {
  const names = Object.keys(params);
  const vals = Object.values(params);
  return new Function(...names, `return \`${this}\`;`)(...vals);
}

const template = 'Example text: ${text}';
const result = template.interpolate({
  text: 'Foo Boo'
});
console.log(result);

อัปเดต ฉันได้ลบการพึ่งพา Lodash แล้ว ES6 มีวิธีการที่เทียบเท่าเพื่อรับกุญแจและค่า


1
สวัสดีโซลูชันของคุณใช้งานได้ดี แต่เมื่อฉันใช้ใน React Native (โหมดการสร้าง) มันจะพ่นข้อผิดพลาด: อักขระไม่ถูกต้อง '' 'แม้ว่ามันจะทำงานเมื่อฉันทำงานในโหมดแก้ไขข้อบกพร่อง ดูเหมือนปัญหาบาเบลความช่วยเหลือใด ๆ
Mohit Pandey

@MohitPandey ฉันได้รับข้อผิดพลาดเดียวกันเมื่อฉันเรียกใช้การทดสอบของรหัสนี้ภายใต้ PhantomJS และมันก็ผ่านใต้โครเมี่ยม หากเป็นเช่นนั้นฉันคิดว่ามี PhantomJS รุ่นเบต้าใหม่พร้อมรองรับ ES6 ที่ดีกว่าคุณสามารถลองติดตั้งได้
Mateusz Moska

1
น่าเสียดายที่มันใช้งานไม่ได้และฉันเขียน regex ลงไปเหมือนกัน เพิ่มเป็นคำตอบเช่นกัน
Mohit Pandey

วิธีการแก้ปัญหานี้จะทำงานเฉพาะในกรณีที่อักขระ "` "back-tick ไม่ปรากฏในสตริงแม่แบบ
SliverNinja - MSFT

ReferenceError: _ is not definedเมื่อผมลองผมได้ รหัสนี้ไม่ใช่ ES6 แต่lodashเฉพาะเจาะจงหรือ ... ?
xpt

29

สิ่งที่คุณขอที่นี่:

//non working code quoted from the question
let b=10;
console.log(a.template());//b:10

มีความเท่าเทียมกันอย่างแน่นอน (ในแง่ของพลังงานและ, เอ่อ, ความปลอดภัย) ถึงeval: ความสามารถในการใช้สตริงที่มีโค้ดและเรียกใช้โค้ดนั้น และความสามารถในการใช้รหัสเพื่อดูตัวแปรท้องถิ่นในสภาพแวดล้อมของผู้โทร

ไม่มีทางใน JS eval()สำหรับฟังก์ชั่นเพื่อดูตัวแปรท้องถิ่นในการโทรของมันคือถ้าฟังก์ชั่นที่เป็น แม้จะFunction()ไม่สามารถทำมัน


เมื่อคุณได้ยินว่ามีบางสิ่งที่เรียกว่า "แม่แบบสตริง" ที่มาถึง JavaScript เป็นเรื่องปกติที่จะถือว่าเป็นไลบรารีแม่แบบในตัวเช่นหนวด มันไม่ใช่ ส่วนใหญ่เป็นเพียงการแก้ไขสตริงและสตริงหลายบรรทัดสำหรับ JS ฉันคิดว่านี่จะเป็นความเข้าใจผิดที่เกิดขึ้นชั่วขณะหนึ่ง :(


2
TBH นั่นคือสิ่งที่ฉันคิดว่ามันเป็น น่าจะมีประโยชน์มาก ๆ
Bryan Rayner

สิ่งนี้ (ยัง) ใช้งานได้หรือไม่ template is not a functionฉันได้รับ
IonicăBizău

2
บล็อกรหัสที่ด้านบนของคำตอบนี้เป็นคำพูดจากคำถาม มันไม่ทำงาน.
Jason Orendorff

27

ไม่ไม่มีวิธีการนี้หากไม่มีการสร้างรหัสแบบไดนามิก

อย่างไรก็ตามฉันได้สร้างฟังก์ชั่นที่จะเปลี่ยนสตริงปกติเป็นฟังก์ชั่นที่สามารถให้กับแผนที่ของค่าโดยใช้สตริงแม่แบบภายใน

สร้างแม่แบบสตริงส่วนสำคัญ

/**
 * Produces a function which uses template strings to do simple interpolation from objects.
 * 
 * Usage:
 *    var makeMeKing = generateTemplateString('${name} is now the king of ${country}!');
 * 
 *    console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
 *    // Logs 'Bryan is now the king of Scotland!'
 */
var generateTemplateString = (function(){
    var cache = {};

    function generateTemplate(template){
        var fn = cache[template];

        if (!fn){
            // Replace ${expressions} (etc) with ${map.expressions}.

            var sanitized = template
                .replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match){
                    return `\$\{map.${match.trim()}\}`;
                    })
                // Afterwards, replace anything that's not ${map.expressions}' (etc) with a blank string.
                .replace(/(\$\{(?!map\.)[^}]+\})/g, '');

            fn = Function('map', `return \`${sanitized}\``);
        }

        return fn;
    }

    return generateTemplate;
})();

การใช้งาน:

var kingMaker = generateTemplateString('${name} is king!');

console.log(kingMaker({name: 'Bryan'}));
// Logs 'Bryan is king!' to the console.

หวังว่านี่จะช่วยใครซักคน หากคุณพบปัญหาเกี่ยวกับรหัสโปรดให้ความช่วยเหลือในการอัพเดท Gist


ขอบคุณ! ฉันใช้สิ่งนี้แทนโซลูชัน javascript sprintf
seangwright

1
ไม่สามารถใช้ได้กับทุกเทมเพลตที่var test = generateTemplateString('/api/${param1}/${param2}/') console.log(test({param1: 'bar', param2: 'foo'}))ส่งคืน/api/bar//
Guillaume Vincent

ขอบคุณคง Regex ได้รวมการแข่งขันนัดเดียวของ $ {param1} / $ {param2} เมื่อมันควรจะมีการแข่งขันสองครั้ง
Bryan Rayner

โปรดทราบว่าอันนี้ใช้งานไม่ได้ใน IE11 เนื่องจากขาดการสนับสนุนสำหรับเห็บหลัง
s.meijer

1
แน่นอนถ้าเบราว์เซอร์ไม่สนับสนุนสตริงแม่แบบวิธีนี้จะใช้ไม่ได้ หากคุณต้องการใช้สตริงแม่แบบในเบราว์เซอร์ที่ไม่รองรับฉันขอแนะนำให้ใช้ภาษาเช่น TypeScript หรือ transpiler เช่น Babel นั่นเป็นวิธีเดียวที่จะทำให้ ES6 เป็นเบราว์เซอร์รุ่นเก่าได้
ไบรอันเร

9

TLDR: https://jsfiddle.net/w3jx07vt/

ทุกคนดูเหมือนจะกังวลเกี่ยวกับการเข้าถึงตัวแปรทำไมไม่เพียงส่งพวกเขาไป? ฉันแน่ใจว่ามันจะไม่ยากเกินไปที่จะรับบริบทตัวแปรในตัวเรียกแล้วส่งมันลง ใช้https://stackoverflow.com/a/6394168/6563504นี้เพื่อรับอุปกรณ์ประกอบฉากจาก obj ฉันไม่สามารถทดสอบให้คุณได้ในตอนนี้ แต่มันควรจะใช้ได้

function renderString(str,obj){
    return str.replace(/\$\{(.+?)\}/g,(match,p1)=>{return index(obj,p1)})
}

ผ่านการทดสอบ นี่คือรหัสเต็ม

function index(obj,is,value) {
    if (typeof is == 'string')
        is=is.split('.');
    if (is.length==1 && value!==undefined)
        return obj[is[0]] = value;
    else if (is.length==0)
        return obj;
    else
        return index(obj[is[0]],is.slice(1), value);
}

function renderString(str,obj){
    return str.replace(/\$\{.+?\}/g,(match)=>{return index(obj,match)})
}

renderString('abc${a}asdas',{a:23,b:44}) //abc23asdas
renderString('abc${a.c}asdas',{a:{c:22,d:55},b:44}) //abc22asdas

@ s.meijer คุณช่วยอธิบายได้ไหม? ฉันใช้รหัสนี้สำเร็จ jsfiddle.net/w3jx07vt
M3D

1
regex ที่ดีกว่าจะช่วยให้คุณไม่ได้เลือก${}ตัวละคร ลอง:/(?!\${)([^{}]*)(?=})/g
Eric Hodonsky

@Relic jsfiddle.net/w3jx07vt/2ฉันไม่สามารถทำงานได้โปรดใส่ใจที่จะให้ยืมและฉันจะอัปเดตโพสต์ของฉันหรือไม่ :)
M3D

ดังนั้นวิธีที่คุณพยายามคว้ามันไม่ได้ช่วยอะไรได้มากนักฉันลงเอยด้วยการใช้สายแทนที่แทน แทนที่จะเพิ่มขั้นตอนของการแก้ไขดังนั้นฉันสามารถใช้สตริงเป็น interp หรือสตริง ไม่แฟนซี แต่มันใช้ได้
Eric Hodonsky

8

ปัญหาที่นี่คือการมีฟังก์ชั่นที่สามารถเข้าถึงตัวแปรของผู้โทร นี่คือเหตุผลที่เราเห็นevalการใช้โดยตรงสำหรับการประมวลผลแม่แบบ ทางออกที่เป็นไปได้คือการสร้างฟังก์ชั่นโดยใช้พารามิเตอร์ทางการชื่อคุณสมบัติของพจนานุกรมและเรียกมันด้วยค่าที่สอดคล้องกันในลำดับเดียวกัน อีกทางเลือกหนึ่งคือการมีสิ่งที่ง่ายเช่นนี้:

var name = "John Smith";
var message = "Hello, my name is ${name}";
console.log(new Function('return `' + message + '`;')());

และสำหรับทุกคนที่ใช้ Babel compiler เราจำเป็นต้องสร้างการปิดซึ่งจดจำสภาพแวดล้อมที่มันถูกสร้างขึ้น:

console.log(new Function('name', 'return `' + message + '`;')(name));

ตัวอย่างข้อมูลแรกของคุณแย่กว่าevalเพราะใช้งานได้กับnameตัวแปรทั่วโลกเท่านั้น
Bergi

@Bergi คำสั่งของคุณถูกต้อง - ขอบเขตของฟังก์ชั่นจะหายไป ฉันต้องการนำเสนอวิธีแก้ปัญหาที่ง่ายและให้ตัวอย่างที่เรียบง่ายของสิ่งที่สามารถทำได้ เราสามารถทำสิ่งต่อไปนี้เพื่อเอาชนะปัญหา: var template = function() { var name = "John Smith"; var message = "Hello, my name is ${name}"; this.local = new Function('return '+ message +';')();}
didinko

ไม่ว่าสิ่งที่จะไม่ทำงาน - new Functionไม่ได้มีการเข้าถึงvar nameในtemplateฟังก์ชั่น
Bergi

สนิปที่สองแก้ไขปัญหาของฉัน ... โหวตขึ้นจากฉัน! ขอบคุณนี้ช่วยแก้ปัญหาชั่วคราวที่เรากำลังมีกับแบบไดนามิกเส้นทางไปยัง iframe :)
คริสบอยด์

7

มีโซลูชั่นที่ดีมากโพสต์ที่นี่ แต่ไม่มีใครเลยที่จะใช้วิธีการ ES6 String.raw นี่คือ contriubution ของฉัน มีข้อ จำกัด ที่สำคัญคือจะยอมรับเฉพาะคุณสมบัติจากวัตถุที่ส่งผ่านหมายความว่าไม่มีการใช้รหัสในเทมเพลต

function parseStringTemplate(str, obj) {
    let parts = str.split(/\$\{(?!\d)[\wæøåÆØÅ]*\}/);
    let args = str.match(/[^{\}]+(?=})/g) || [];
    let parameters = args.map(argument => obj[argument] || (obj[argument] === undefined ? "" : obj[argument]));
    return String.raw({ raw: parts }, ...parameters);
}
let template = "Hello, ${name}! Are you ${age} years old?";
let values = { name: "John Doe", age: 18 };

parseStringTemplate(template, values);
// output: Hello, John Doe! Are you 18 years old?
  1. แยกสตริงออกเป็นส่วนข้อความที่ไม่ใช่ข้อโต้แย้ง ดูregex
    parts: ["Hello, ", "! Are you ", " years old?"]
  2. แยกสตริงเป็นชื่อคุณสมบัติ อาร์เรย์ว่างถ้าการแข่งขันล้มเหลว
    args: ["name", "age"]
  3. แม็พพารามิเตอร์จากobjตามชื่อคุณสมบัติ โซลูชันถูก จำกัด โดยการทำแผนที่ระดับเดียวแบบตื้น ค่าที่ไม่ได้กำหนดจะถูกแทนที่ด้วยสตริงว่าง แต่ยอมรับค่าเท็จอื่น ๆ
    parameters: ["John Doe", 18]
  4. ใช้String.raw(...)และส่งคืนผลลัพธ์

String.raw ให้คุณค่าที่นี่จริงหรือเปล่า ดูเหมือนว่าคุณกำลังทำงานทั้งหมดในการแยกวิเคราะห์สตริงและติดตามสิ่งที่เป็น substitions สิ่งนี้แตกต่างจากการโทร.replace()ซ้ำบ่อย ๆ หรือไม่?
Steve Bennett

จุดยุติธรรม @SteveBennett ฉันมีปัญหาบางอย่างเปลี่ยนสตริงปกติเป็นสตริงแม่แบบและพบวิธีแก้ปัญหาด้วยการสร้างวัตถุดิบด้วยตนเอง ฉันเดาว่ามันจะลด String.raw เป็นวิธีการเรียงต่อกัน แต่ฉันคิดว่ามันทำงานได้ดีทีเดียว ฉันต้องการเห็นวิธีแก้ปัญหาที่ดีด้วย.replace():) ฉันคิดว่าการอ่านเป็นสิ่งสำคัญดังนั้นในขณะที่ใช้การแสดงออกปกติฉันพยายามตั้งชื่อพวกเขาเพื่อช่วยให้เข้าใจได้ทั้งหมด ...
pekaaw

6

คล้ายกับคำตอบของ Daniel (และส่วนสำคัญของ s.meijer ) แต่สามารถอ่านได้มากขึ้น:

const regex = /\${[^{]+}/g;

export default function interpolate(template, variables, fallback) {
    return template.replace(regex, (match) => {
        const path = match.slice(2, -1).trim();
        return getObjPath(path, variables, fallback);
    });
}

//get the specified property or nested property of an object
function getObjPath(path, obj, fallback = '') {
    return path.split('.').reduce((res, key) => res[key] || fallback, obj);
}

หมายเหตุ: สิ่งนี้จะปรับปรุงต้นฉบับดั้งเดิมของ s.meijer เล็กน้อยเนื่องจากจะไม่ตรงกับสิ่งต่าง ๆ เช่น${foo{bar}(regex อนุญาตเฉพาะอักขระรั้งที่ไม่ใช่หยิกภายใน${และ})


UPDATE: ฉันถูกขอตัวอย่างโดยใช้สิ่งนี้ดังนั้นคุณไปที่นี่:

const replacements = {
    name: 'Bob',
    age: 37
}

interpolate('My name is ${name}, and I am ${age}.', replacements)

คุณสามารถโพสต์ตัวอย่างโดยใช้สิ่งนี้ได้จริงหรือ จาวาสคริปต์นี้เกินกว่าฉันเล็กน้อย ฉันขอแนะนำ regex ของ/\$\{(.*?)(?!\$\{)\}/g(เพื่อจัดการวงเล็บปีกนกรัง) ฉันมีวิธีแก้ปัญหาการทำงาน แต่ฉันไม่แน่ใจว่ามันเป็นแบบพกพาเหมือนของคุณดังนั้นฉันชอบที่จะเห็นว่าสิ่งนี้ควรนำไปใช้ในหน้าได้อย่างไร eval()เหมืองแร่ยังใช้
ปกติ Joe

ฉันเดินไปข้างหน้าและโพสต์คำตอบเป็นอย่างดีและฉันชอบที่ความคิดเห็นของคุณเกี่ยวกับวิธีการที่จะทำให้ปลอดภัยมากขึ้นและประสิทธิภาพใจ: stackoverflow.com/a/48294208
ปกติ Joe

@RegularJoe ฉันได้เพิ่มตัวอย่าง เป้าหมายของฉันคือการทำให้มันง่าย แต่คุณพูดถูกถ้าคุณต้องการที่จะจัดการกับเครื่องมือจัดฟันที่ซ้อนกันคุณจะต้องเปลี่ยน regex อย่างไรก็ตามฉันไม่สามารถนึกถึงกรณีการใช้งานสำหรับเมื่อประเมินสตริงปกติราวกับว่ามันเป็นตัวอักษรแม่แบบ (วัตถุประสงค์ทั้งหมดของฟังก์ชั่นนี้) สิ่งที่คุณมีในใจ?
Matt Browne

นอกจากนี้ฉันไม่ใช่ผู้เชี่ยวชาญด้านประสิทธิภาพและผู้เชี่ยวชาญด้านความปลอดภัย คำตอบของฉันเป็นเพียงการรวมสองคำตอบก่อนหน้านี้ แต่ฉันจะบอกว่าการใช้ให้evalคุณเปิดกว้างกับข้อผิดพลาดที่อาจทำให้เกิดปัญหาด้านความปลอดภัยในขณะที่รุ่นทั้งหมดของฉันกำลังทำคือการค้นหาคุณสมบัติบนวัตถุจากเส้นทางจุดแยกซึ่งควรจะปลอดภัย
Matt Browne

5

คุณสามารถใช้ต้นแบบสตริงตัวอย่างเช่น

String.prototype.toTemplate=function(){
    return eval('`'+this+'`');
}
//...
var a="b:${b}";
var b=10;
console.log(a.toTemplate());//b:10

แต่คำตอบของคำถามเดิมไม่มีทาง


5

ฉันชอบคำตอบของ s.meijer และเขียนเวอร์ชันของฉันเองตาม:

function parseTemplate(template, map, fallback) {
    return template.replace(/\$\{[^}]+\}/g, (match) => 
        match
            .slice(2, -1)
            .trim()
            .split(".")
            .reduce(
                (searchObject, key) => searchObject[key] || fallback || match,
                map
            )
    );
}

1
เรียบร้อย! เรียบร้อยจริง ๆ !
xpt

4

ฉันต้องการวิธีนี้พร้อมรองรับ Internet Explorer มันเปิดออกเห็บกลับไม่ได้รับการสนับสนุนโดยแม้แต่ IE11 นอกจากนี้ยัง; ใช้งานevalหรือเทียบเท่าFunctionไม่รู้สึกถูก

สำหรับคนที่สังเกตเห็น; ฉันใช้ backticks ด้วย แต่คอมไพเลอร์เหล่านี้จะถูกลบออกอย่าง Babel วิธีการที่แนะนำโดยคนอื่นขึ้นอยู่กับพวกเขาในเวลาทำงาน ดังกล่าวก่อนหน้านี้; นี่เป็นปัญหาใน IE11 และต่ำกว่า

ดังนั้นนี่คือสิ่งที่ฉันมาด้วย:

function get(path, obj, fb = `$\{${path}}`) {
  return path.split('.').reduce((res, key) => res[key] || fb, obj);
}

function parseTpl(template, map, fallback) {
  return template.replace(/\$\{.+?}/g, (match) => {
    const path = match.substr(2, match.length - 3).trim();
    return get(path, map, fallback);
  });
}

ตัวอย่างผลลัพธ์:

const data = { person: { name: 'John', age: 18 } };

parseTpl('Hi ${person.name} (${person.age})', data);
// output: Hi John (18)

parseTpl('Hello ${person.name} from ${person.city}', data);
// output: Hello John from ${person.city}

parseTpl('Hello ${person.name} from ${person.city}', data, '-');
// output: Hello John from -

"การใช้ eval หรือฟังก์ชั่นเทียบเท่าไม่รู้สึกถูกต้อง" ... ใช่ ... ฉันเห็นด้วย แต่ฉันคิดว่านี่เป็นหนึ่งในกรณีการใช้งานเพียงไม่กี่กรณีที่ใคร ๆ ก็สามารถพูดได้ว่า "mmhkay มาใช้กันเถอะ" โปรดตรวจสอบjsperf.com/es6-string-tmpl - เป็นกรณีการใช้งานจริงของฉัน ใช้ฟังก์ชั่นของคุณ (ที่มี regexp เช่นเดียวกับของฉัน) และของฉัน (eval + ตัวอักษรสตริง) ขอบคุณ! :)
Andrea Puddu

@AndreaPuddu ประสิทธิภาพของคุณดีขึ้นแน่นอน แต่แล้วอีกครั้ง; สตริงแม่แบบไม่ได้รับการสนับสนุนใน IE ดังนั้นeval('`' + taggedURL + '`')ก็ไม่ทำงาน
s.meijer

"ดูเหมือนว่า" ดีกว่าผมว่าเพราะมันผ่านการทดสอบในการแยก ... evalวัตถุประสงค์เฉพาะของการทดสอบที่กำลังจะดูปัญหาประสิทธิภาพการทำงานที่อาจเกิดขึ้นโดยใช้ เกี่ยวกับเทมเพลตตัวอักษร: ขอบคุณที่ชี้ให้เห็นอีกครั้ง ฉันใช้ Babel เพื่อ transpile รหัสของฉัน แต่ฟังก์ชั่นของฉันจะยังใช้งานไม่ได้😐
Andrea Puddu

3

ขณะนี้ฉันไม่สามารถแสดงความคิดเห็นกับคำตอบที่มีอยู่ดังนั้นฉันไม่สามารถแสดงความคิดเห็นโดยตรงกับการตอบสนองที่ยอดเยี่ยมของ Bryan Raynor ดังนั้นการตอบสนองนี้จะปรับปรุงคำตอบของเขาด้วยการแก้ไขเล็กน้อย

กล่าวโดยสรุปฟังก์ชั่นของเขาล้มเหลวในการสร้างฟังก์ชั่นแคชจริง ๆ ดังนั้นมันจะสร้างขึ้นมาใหม่เสมอไม่ว่าจะเห็นเทมเพลตมาก่อนหรือไม่ นี่คือรหัสที่ถูกต้อง:

    /**
     * Produces a function which uses template strings to do simple interpolation from objects.
     * 
     * Usage:
     *    var makeMeKing = generateTemplateString('${name} is now the king of ${country}!');
     * 
     *    console.log(makeMeKing({ name: 'Bryan', country: 'Scotland'}));
     *    // Logs 'Bryan is now the king of Scotland!'
     */
    var generateTemplateString = (function(){
        var cache = {};

        function generateTemplate(template){
            var fn = cache[template];

            if (!fn){
                // Replace ${expressions} (etc) with ${map.expressions}.

                var sanitized = template
                    .replace(/\$\{([\s]*[^;\s\{]+[\s]*)\}/g, function(_, match){
                        return `\$\{map.${match.trim()}\}`;
                    })
                    // Afterwards, replace anything that's not ${map.expressions}' (etc) with a blank string.
                    .replace(/(\$\{(?!map\.)[^}]+\})/g, '');

                fn = cache[template] = Function('map', `return \`${sanitized}\``);
            }

            return fn;
        };

        return generateTemplate;
    })();

3

@Mateusz Moska โซลูชันใช้งานได้ดี แต่เมื่อฉันใช้ใน React Native (โหมดการสร้าง) มันจะพ่นข้อผิดพลาด: อักขระไม่ถูกต้อง '' 'แม้ว่ามันจะทำงานเมื่อฉันรันในโหมดดีบัก

ดังนั้นฉันจึงเขียนวิธีการแก้ปัญหาของตัวเองโดยใช้ regex

String.prototype.interpolate = function(params) {
  let template = this
  for (let key in params) {
    template = template.replace(new RegExp('\\$\\{' + key + '\\}', 'g'), params[key])
  }
  return template
}

const template = 'Example text: ${text}',
  result = template.interpolate({
    text: 'Foo Boo'
  })

console.log(result)

ตัวอย่าง: https://es6console.com/j31pqx1p/

หมายเหตุ:เนื่องจากฉันไม่ทราบสาเหตุที่แท้จริงของปัญหาฉันจึงยกตั๋วในธุรกรรมซื้อคืนแบบตอบสนองพื้นเมืองhttps://github.com/facebook/react-native/issues/14107เพื่อให้พวกเขาสามารถ แก้ไข / แนะนำฉันเกี่ยวกับเรื่องเดียวกัน :)


สิ่งนี้จะสนับสนุนเทมเพลตที่มีอักขระย้อนหลัง แต่แทนที่จะพยายามที่จะคิดค้นรูปแบบการสร้างแม่แบบที่คุณอาจจะดีกว่าเพียงแค่ใช้หนวดหรือคล้ายกัน ขึ้นอยู่กับความซับซ้อนของแม่แบบของคุณนี่คือวิธีการบังคับเดรัจฉานที่ไม่พิจารณากรณีขอบ - ที่สำคัญอาจมีรูปแบบ regex พิเศษ
SliverNinja - MSFT

2

ยังคงมีชีวิตชีวา แต่ดูเหมือนว่าควบคุมได้มากกว่าการใช้ eval แบบเปลือย:

const vm = require('vm')
const moment = require('moment')


let template = '### ${context.hours_worked[0].value} \n Hours worked \n #### ${Math.abs(context.hours_worked_avg_diff[0].value)}% ${fns.gt0(context.hours_worked_avg_diff[0].value, "more", "less")} than usual on ${fns.getDOW(new Date())}'
let context = {
  hours_worked:[{value:10}],
  hours_worked_avg_diff:[{value:10}],

}


function getDOW(now) {
  return moment(now).locale('es').format('dddd')
}

function gt0(_in, tVal, fVal) {
  return _in >0 ? tVal: fVal
}



function templateIt(context, template) {
  const script = new vm.Script('`'+template+'`')
  return script.runInNewContext({context, fns:{getDOW, gt0 }})
}

console.log(templateIt(context, template))

https://repl.it/IdVt/3


1

วิธีนี้ใช้ไม่ได้กับ ES6:

function render(template, opts) {
  return new Function(
    'return new Function (' + Object.keys(opts).reduce((args, arg) => args += '\'' + arg + '\',', '') + '\'return `' + template.replace(/(^|[^\\])'/g, '$1\\\'') + '`;\'' +
    ').apply(null, ' + JSON.stringify(Object.keys(opts).reduce((vals, key) => vals.push(opts[key]) && vals, [])) + ');'
  )();
}

render("hello ${ name }", {name:'mo'}); // "hello mo"

หมายเหตุ: ตัวFunctionสร้างจะถูกสร้างในขอบเขตทั่วโลกซึ่งอาจทำให้ตัวแปรทั่วโลกถูกเขียนทับโดยแม่แบบเช่นrender("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});


0

เนื่องจากเรากำลังคิดค้นสิ่งใหม่บนสิ่งที่จะเป็นคุณสมบัติที่น่ารักในจาวาสคริปต์

ฉันใช้eval()ซึ่งไม่ปลอดภัย แต่จาวาสคริปต์ไม่ปลอดภัย ฉันยอมรับอย่างง่ายดายว่าฉันไม่เก่งจาวาสคริปต์ แต่ฉันต้องการและฉันต้องการคำตอบดังนั้นฉันจึงทำ

ฉันเลือกที่จะสร้างความแปรปรวนให้กับตัวแปร@มากกว่า$โดยเฉพาะเพราะฉันต้องการใช้คุณสมบัติหลายบรรทัดของตัวอักษรโดยไม่ประเมินว่ามันพร้อมหรือยัง ดังนั้นตัวแปรไวยากรณ์คือ@{OptionalObject.OptionalObjectN.VARIABLE_NAME}

ฉันไม่ใช่ผู้เชี่ยวชาญจาวาสคริปต์ดังนั้นฉันยินดีที่จะให้คำแนะนำในการปรับปรุง แต่ ...

var prsLiteral, prsRegex = /\@\{(.*?)(?!\@\{)\}/g
for(i = 0; i < myResultSet.length; i++) {
    prsLiteral = rt.replace(prsRegex,function (match,varname) {
        return eval(varname + "[" + i + "]");
        // you could instead use return eval(varname) if you're not looping.
    })
    console.log(prsLiteral);
}

การใช้งานง่ายมากดังต่อไปนี้

myResultSet = {totalrecords: 2,
Name: ["Bob", "Stephanie"],
Age: [37,22]};

rt = `My name is @{myResultSet.Name}, and I am @{myResultSet.Age}.`

var prsLiteral, prsRegex = /\@\{(.*?)(?!\@\{)\}/g
for(i = 0; i < myResultSet.totalrecords; i++) {
    prsLiteral = rt.replace(prsRegex,function (match,varname) {
        return eval(varname + "[" + i + "]");
        // you could instead use return eval(varname) if you're not looping.
    })
    console.log(prsLiteral);
}

@{{variable}}ในการดำเนินงานที่เกิดขึ้นจริงของฉันฉันเลือกที่จะใช้ เครื่องมือจัดฟันอีกหนึ่งชุด ไม่น่าแปลกใจที่จะพบกับสิ่งที่ไม่คาดคิด regex สำหรับที่จะมีลักษณะ/\@\{\{(.*?)(?!\@\{\{)\}\}/g

เพื่อให้อ่านง่ายขึ้น

\@\{\{    # opening sequence, @{{ literally.
(.*?)     # capturing the variable name
          # ^ captures only until it reaches the closing sequence
(?!       # negative lookahead, making sure the following
          # ^ pattern is not found ahead of the current character
  \@\{\{  # same as opening sequence, if you change that, change this
)
\}\}      # closing sequence.

หากคุณไม่ได้มีประสบการณ์กับ regex กฎปลอดภัยสวยคือการหนีตัวละครทุกตัวที่ไม่ใช่ตัวเลขและไม่เคยไม่จำเป็นต้องหนีตัวอักษรเป็นตัวอักษรหนีจำนวนมากมีความหมายพิเศษแทบทุกรสชาติของ regex


0

คุณควรลองโมดูล JS เล็ก ๆ นี้โดย Andrea Giammarchi จาก github: https://github.com/WebReflection/backtick-template

/*! (C) 2017 Andrea Giammarchi - MIT Style License */
function template(fn, $str, $object) {'use strict';
  var
    stringify = JSON.stringify,
    hasTransformer = typeof fn === 'function',
    str = hasTransformer ? $str : fn,
    object = hasTransformer ? $object : $str,
    i = 0, length = str.length,
    strings = i < length ? [] : ['""'],
    values = hasTransformer ? [] : strings,
    open, close, counter
  ;
  while (i < length) {
    open = str.indexOf('${', i);
    if (-1 < open) {
      strings.push(stringify(str.slice(i, open)));
      open += 2;
      close = open;
      counter = 1;
      while (close < length) {
        switch (str.charAt(close++)) {
          case '}': counter -= 1; break;
          case '{': counter += 1; break;
        }
        if (counter < 1) {
          values.push('(' + str.slice(open, close - 1) + ')');
          break;
        }
      }
      i = close;
    } else {
      strings.push(stringify(str.slice(i)));
      i = length;
    }
  }
  if (hasTransformer) {
    str = 'function' + (Math.random() * 1e5 | 0);
    if (strings.length === values.length) strings.push('""');
    strings = [
      str,
      'with(this)return ' + str + '([' + strings + ']' + (
        values.length ? (',' + values.join(',')) : ''
      ) + ')'
    ];
  } else {
    strings = ['with(this)return ' + strings.join('+')];
  }
  return Function.apply(null, strings).apply(
    object,
    hasTransformer ? [fn] : []
  );
}

template.asMethod = function (fn, object) {'use strict';
  return typeof fn === 'function' ?
    template(fn, this, object) :
    template(this, fn);
};

การสาธิต (การทดสอบทั้งหมดต่อไปนี้คืนค่าจริง):

const info = 'template';
// just string
`some ${info}` === template('some ${info}', {info});

// passing through a transformer
transform `some ${info}` === template(transform, 'some ${info}', {info});

// using it as String method
String.prototype.template = template.asMethod;

`some ${info}` === 'some ${info}'.template({info});

transform `some ${info}` === 'some ${info}'.template(transform, {info});

0

ฉันสร้างโซลูชันของฉันเองโดยพิมพ์คำอธิบายเป็นฟังก์ชัน

export class Foo {
...
description?: Object;
...
}

let myFoo:Foo = {
...
  description: (a,b) => `Welcome ${a}, glad to see you like the ${b} section`.
...
}

และทำเช่นนั้น:

let myDescription = myFoo.description('Bar', 'bar');

0

แทนที่จะใช้ eval ที่ดีกว่าคือใช้เพื่อ regex

Eval ไม่แนะนำ & ไม่แนะนำให้ใช้ดังนั้นโปรดอย่าใช้มัน ( mdn eval )

 let b = 10;
 let a="b:${b}";

let response = a.replace(/\${\w+}/ ,b);
conssole.log(response);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.