Chuyển đổi một chuỗi thành một chuỗi mẫu


147

Có thể tạo một chuỗi mẫu như một chuỗi thông thường

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

sau đó chuyển đổi nó thành một chuỗi mẫu

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

không có eval, new Functionvà các phương tiện tạo mã động khác?


5
bạn đã tìm ra cách để đạt được điều này? Tôi có thể cần phải làm điều đó một ngày và tò mò muốn biết những gì bạn đã đến.
Bryan Rayner

@BryanRayner cho biết chương trình js của bạn đang cố gắng tìm nạp dữ liệu từ API còn lại, có url nằm trong tệp config.js dưới dạng chuỗi "/ resource / <resource_id> / update /" và bạn tự động đặt "resource_id" từ chương trình của mình . Trừ khi bạn muốn chia url đó thành các phần và lưu ở các khu vực khác nhau, bạn cần một số loại xử lý mẫu chuỗi.
Ryu_hayabusa


Thay vì sử dụng eval tốt hơn là sử dụng để regex Eval, nó không được khuyến khích và không được khuyến khích, vì vậy vui lòng không sử dụng developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/,! cho b = 10; hãy để a = "b: $ {b}"; hãy trả lời = a.replace (/ \ $ {\ w +} /, b); conssole.log (phản hồi);
Vijay Palaskar

Câu trả lời:


79

Vì chuỗi mẫu của bạn phải được tham chiếu đến bbiến động (trong thời gian chạy), vì vậy câu trả lời là: KHÔNG, không thể thực hiện được nếu không tạo mã động.

Nhưng với evalnó khá đơn giản:

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

7
eval không an toàn, các phương tiện tạo mã động khác cũng vậy
KOLANICH 22/03/2015

8
@KOLANICH Cụ thể trong trường hợp đó - thoát khỏi dấu ngoặc kép trong achuỗi và nó sẽ không an toàn hơn nhiều : let tpl = eval('`'+a.replace(/`/g,'\\`')+'`');. Tôi nghĩ quan trọng hơn là evalngăn chặn trình biên dịch để tối ưu hóa mã của bạn. Nhưng tôi nghĩ nó không liên quan đến câu hỏi này.
alexpods 22/03/2015

3
Trong thực tế, bạn cũng có thể chạy các chức năng bên trong chuỗi mẫu.
KOLANICH 22/03/2015

9
@KOLANICH Xin lỗi bạn không thích eval. Tuy nhiên, hãy nhớ rằng một mẫu chữ chính nó là một hình thức eval. Hai ví dụ: var test = Result: ${alert('hello')}; kiểm tra var = Result: ${b=4}; Cả hai sẽ kết thúc việc thực thi mã tùy ý trong ngữ cảnh của tập lệnh. Nếu bạn muốn cho phép các chuỗi tùy ý, bạn cũng có thể cho phép eval.
Manngo

6
Hãy cẩn thận. Vì một cái gì đó giống như babel sẽ không dịch mã này, mã này sẽ KHÔNG hoạt động trong IE
cgsd

79

Trong dự án của tôi, tôi đã tạo ra một cái gì đó như thế này với 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);

CẬP NHẬT Tôi đã loại bỏ phụ thuộc lodash, ES6 có các phương thức tương đương để lấy khóa và giá trị.


1
Xin chào, Giải pháp của bạn hoạt động rất tốt, nhưng khi tôi sử dụng nó trong React Native (chế độ xây dựng), nó sẽ báo lỗi: Ký tự không hợp lệ '`' , mặc dù nó hoạt động khi tôi chạy ở chế độ gỡ lỗi. Hình như, vấn đề babel, có giúp được gì không?
Mohit Pandey

@MohitPandey Tôi đã gặp lỗi tương tự khi tôi đang chạy thử nghiệm mã này dưới PhantomJS và nó đã chuyển qua chrome. Nếu đó là trường hợp, tôi nghĩ rằng có phiên bản beta mới của PhantomJS trên đường với sự hỗ trợ tốt hơn cho ES6, bạn có thể thử cài đặt nó.
Mateusz Moska

1
Thật không may, nó không hoạt động và tôi đã viết một regex cho cùng. Thêm vào như câu trả lời là tốt.
Mohit Pandey

giải pháp này chỉ hoạt động nếu ký tự "` "back-tick không có trong chuỗi mẫu
SliverNinja - MSFT

Khi tôi thử nó tôi đã nhận được ReferenceError: _ is not defined. Có phải mã không phải ES6 nhưng lodashcụ thể, hay ...?
xpt

29

Những gì bạn đang yêu cầu ở đây:

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

hoàn toàn tương đương (về sức mạnh và, er, an toàn) với eval: khả năng lấy một chuỗi chứa mã và thực thi mã đó; và khả năng cho mã được thực thi để xem các biến cục bộ trong môi trường của trình gọi.

Không có cách nào trong JS cho một hàm để xem các biến cục bộ trong trình gọi của nó, trừ khi hàm đó là eval(). Thậm chí Function()không thể làm điều đó.


Khi bạn nghe thấy có một thứ gọi là "chuỗi mẫu" đến với JavaScript, sẽ tự nhiên cho rằng đó là thư viện mẫu có sẵn, như Mustache. Không phải vậy. Nó chủ yếu chỉ là nội suy chuỗi và chuỗi đa dòng cho JS. Tôi nghĩ rằng đây sẽ là một quan niệm sai lầm phổ biến trong một thời gian, mặc dù. :


2
TBH đó là những gì tôi nghĩ đó là. Sẽ rất, rất tiện dụng.
Bryan Rayner

Điều này (vẫn) hoạt động? Tôi đang nhận được template is not a function.
Ionică Bizău

2
Khối mã ở đầu câu trả lời này là một trích dẫn từ câu hỏi. Nó không hoạt động.
Jason Orendorff

27

Không, không có cách nào để làm điều này mà không tạo mã động.

Tuy nhiên, tôi đã tạo một hàm sẽ biến một chuỗi thông thường thành một hàm có thể được cung cấp với bản đồ các giá trị, sử dụng các chuỗi mẫu bên trong.

Tạo sơ đồ chuỗi mẫu

/**
 * 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;
})();

Sử dụng:

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

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

Hy vọng điều này sẽ giúp ai đó. Nếu bạn tìm thấy một vấn đề với mã, xin vui lòng cập nhật Gist.


Cảm ơn! Tôi đã sử dụng điều này thay vì một giải pháp chạy nước rút javascript.
seangwright

1
không hoạt động cho mỗi lần var test = generateTemplateString('/api/${param1}/${param2}/') console.log(test({param1: 'bar', param2: 'foo'}))trả lại mẫu/api/bar//
Guillaume Vincent

Cảm ơn, đã sửa. Regex bao gồm một trận đấu duy nhất là $ {param1} / $ {param2} khi nó phải có hai trận đấu.
Bryan Rayner

Lưu ý rằng cái này không hoạt động trong IE11, vì thiếu hỗ trợ cho các dấu tick ngược.
s.meijer

1
Tất nhiên, nếu các chuỗi mẫu không được trình duyệt hỗ trợ, phương pháp này sẽ không hoạt động. Nếu bạn muốn sử dụng các chuỗi mẫu trong các trình duyệt không được hỗ trợ, tôi khuyên bạn nên sử dụng ngôn ngữ như TypeScript hoặc bộ chuyển mã như Babel; Đó là cách duy nhất để đưa ES6 vào các trình duyệt cũ.
Bryan Rayner

9

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

Mọi người dường như lo lắng về việc truy cập các biến, tại sao không vượt qua chúng? Tôi chắc chắn sẽ không quá khó để có được bối cảnh biến trong người gọi và chuyển nó xuống. Sử dụng https://stackoverflow.com/a/6394168/6563504 để nhận đạo cụ từ obj. Tôi không thể kiểm tra cho bạn ngay bây giờ, nhưng điều này sẽ làm việc.

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

Thử nghiệm. Đây là mã đầy đủ.

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 bạn có thể giải thích? Tôi đang sử dụng mã này thành công. jsfiddle.net/w3jx07vt
M3D

1
Regex tốt hơn sẽ cho phép bạn không chọn các ${}ký tự. Hãy thử:/(?!\${)([^{}]*)(?=})/g
Eric Hodonsky

@Relic jsfiddle.net/w3jx07vt/2 Tôi không thể làm việc đó, quan tâm để giúp một tay và tôi sẽ cập nhật bài viết của mình? :)
M3D

Vì vậy, cách bạn đang cố lấy nó thực sự sẽ không giúp được gì nhiều, cuối cùng tôi đã thực hiện thay thế một chuỗi. Thay vì thêm một bước nội suy, vì vậy tôi có thể sử dụng chuỗi dưới dạng interp hoặc chuỗi. Không ưa thích, nhưng nó đã làm việc.
Eric Hodonsky

8

Vấn đề ở đây là có một hàm có quyền truy cập vào các biến của trình gọi của nó. Đây là lý do tại sao chúng tôi thấy trực tiếp evalđược sử dụng để xử lý mẫu. Một giải pháp khả thi sẽ là tạo ra một hàm lấy các tham số chính thức được đặt tên theo thuộc tính của từ điển và gọi nó với các giá trị tương ứng theo cùng một thứ tự. Một cách khác là có một cái gì đó đơn giản như sau:

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

Và đối với bất kỳ ai sử dụng trình biên dịch Babel, chúng ta cần tạo bao đóng để ghi nhớ môi trường mà nó được tạo:

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

Đoạn mã đầu tiên của bạn thực sự tồi tệ hơn evalbởi vì nó chỉ hoạt động với một namebiến toàn cục
Bergi

@Bergi Tuyên bố của bạn là hợp lệ - phạm vi của chức năng sẽ bị mất. Tôi muốn trình bày một giải pháp dễ dàng cho vấn đề và cung cấp một ví dụ đơn giản về những gì có thể được thực hiện. Người ta có thể chỉ cần đưa ra những điều sau đây để khắc phục vấn đề: var template = function() { var name = "John Smith"; var message = "Hello, my name is ${name}"; this.local = new Function('return '+ tin nhắn +';')();}
didinko

Không, đó chính xác là những gì sẽ không hoạt động - new Functionkhông có quyền truy cập var namevào templatechức năng.
Bergi

Snip thứ hai đã khắc phục vấn đề của tôi ... Hãy bỏ phiếu từ tôi! Cảm ơn, điều này đã giúp giải quyết vấn đề tạm thời mà chúng tôi gặp phải khi định tuyến động tới iframe :)
Kris Boyd

7

Có nhiều giải pháp tốt được đăng ở đây, nhưng chưa có giải pháp nào sử dụng phương thức ES6 String.raw . Đây là sự chống đối của tôi. Nó có một hạn chế quan trọng ở chỗ nó sẽ chỉ chấp nhận các thuộc tính từ một đối tượng được truyền vào, có nghĩa là không có mã thực thi nào trong mẫu sẽ hoạt động.

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. Chia chuỗi thành các phần văn bản không đối số. Xem regex .
    parts: ["Hello, ", "! Are you ", " years old?"]
  2. Chia chuỗi thành tên thuộc tính. Mảng trống nếu khớp không thành công.
    args: ["name", "age"]
  3. Thông số bản đồ từ objtên tài sản. Giải pháp được giới hạn bởi ánh xạ nông một cấp. Các giá trị không xác định được thay thế bằng một chuỗi rỗng, nhưng các giá trị giả khác được chấp nhận.
    parameters: ["John Doe", 18]
  4. Sử dụng String.raw(...)và trả lại kết quả.

Vì tò mò, String.raw thực sự cung cấp giá trị gì ở đây? Có vẻ như bạn đang làm tất cả công việc phân tích chuỗi và theo dõi các trạm biến áp là gì. Điều này có khác nhiều so với việc gọi điện .replace()liên tục không?
Steve Bennett

Điểm công bằng, @SteveBennett. Tôi đã gặp một số vấn đề khi biến một chuỗi bình thường thành một chuỗi mẫu và tự mình tìm ra giải pháp bằng cách xây dựng đối tượng thô. Tôi đoán nó giảm String.raw thành một phương thức nối, nhưng tôi nghĩ nó hoạt động khá tốt. .replace()Mặc dù vậy, tôi muốn thấy một giải pháp tốt với :) Tôi nghĩ rằng khả năng đọc là quan trọng, vì vậy trong khi sử dụng các biểu thức chính quy, tôi cố gắng đặt tên cho chúng để giúp hiểu tất cả ...
pekaaw

6

Tương tự như câu trả lời của Daniel (và ý chính của s.meijer ) nhưng dễ đọc hơn:

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);
}

Lưu ý: Điều này cải thiện một chút bản gốc của s.meijer, vì nó sẽ không khớp với những thứ như ${foo{bar}(regex chỉ cho phép các ký tự dấu ngoặc không cong bên trong ${}).


CẬP NHẬT: Tôi đã được yêu cầu một ví dụ sử dụng điều này, vì vậy ở đây bạn đi:

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

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

Bạn có thể gửi một ví dụ thực sự sử dụng này? Javascript này là một chút ngoài tôi. Tôi muốn đề xuất một regex của /\$\{(.*?)(?!\$\{)\}/g(để xử lý các dấu ngoặc nhọn lồng). Tôi có một giải pháp hiệu quả nhưng tôi không chắc nó có khả năng di động như của bạn không, vì vậy tôi rất muốn xem cách thực hiện điều này trong một trang. Của tôi cũng sử dụng eval().
Joe thường xuyên

Tôi đã tiếp tục và đăng một câu trả lời, và tôi rất thích phản hồi của bạn về cách làm cho nó an toàn và hiệu quả hơn: stackoverflow.com/a/48294208 .
Joe thường xuyên

@RegularJoe Tôi đã thêm một ví dụ. Mục tiêu của tôi là giữ cho nó đơn giản, nhưng bạn nói đúng rằng nếu bạn muốn xử lý các dấu ngoặc nhọn lồng nhau, bạn sẽ cần phải thay đổi biểu thức chính quy. Tuy nhiên, tôi không thể nghĩ ra trường hợp sử dụng cho việc đó khi đánh giá một chuỗi thông thường như thể nó là một mẫu bằng chữ (toàn bộ mục đích của hàm này). Bạn đã nghĩ gì vậy?
Matt Browne

Ngoài ra, tôi không phải là chuyên gia hiệu suất cũng không phải chuyên gia bảo mật; Câu trả lời của tôi thực sự chỉ là kết hợp hai câu trả lời trước. Nhưng tôi sẽ nói rằng việc sử dụng evalkhiến bạn cởi mở hơn rất nhiều đối với các lỗi có thể gây ra sự cố bảo mật, trong khi tất cả các phiên bản của tôi đang làm là tìm kiếm một thuộc tính trên một đối tượng từ một đường phân cách bằng dấu chấm, nên an toàn.
Matt Browne

5

Bạn có thể sử dụng nguyên mẫu chuỗi, ví dụ

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

Nhưng câu trả lời của câu hỏi ban đầu là không có cách nào.


5

Tôi thích câu trả lời của s.meijer và viết phiên bản của riêng tôi dựa trên:

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
Khéo léo! Thực sự gọn gàng!
xpt

4

Tôi yêu cầu phương pháp này với sự hỗ trợ cho Internet Explorer. Hóa ra các dấu tick phía sau không được hỗ trợ bởi ngay cả IE11. Cũng thế; sử dụng evalhoặc tương đương Functionkhông cảm thấy đúng.

Đối với một trong những thông báo; Tôi cũng sử dụng backticks, nhưng những cái này được gỡ bỏ bởi trình biên dịch như babel. Các phương pháp được đề xuất bởi những người khác, phụ thuộc vào chúng vào thời gian chạy. Như đã nói trước; đây là một vấn đề trong IE11 và thấp hơn.

Vì vậy, đây là những gì tôi đã đưa ra:

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);
  });
}

Ví dụ đầu ra:

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 -

"sử dụng eval hoặc nó tương đương Chức năng không cảm thấy đúng." ... Vâng ... Tôi đồng ý, nhưng tôi nghĩ rằng đây là một trong số rất ít trường hợp sử dụng trong đó người ta có thể nói, "mmhkay, hãy sử dụng nó". Vui lòng kiểm tra jsperf.com/es6-opes-tmpl - đó là trường hợp sử dụng thực tế của tôi. Sử dụng chức năng của bạn (có cùng biểu thức chính quy như của tôi) và của tôi (eval + chuỗi ký tự). Cảm ơn! :)
Andrea Puddu

@AndreaPuddu, hiệu suất của bạn thực sự tốt hơn. Nhưng sau đó một lần nữa; chuỗi mẫu không được hỗ trợ trong IE. Vì vậy, eval('`' + taggedURL + '`')đơn giản là không làm việc.
s.meijer

"Có vẻ" tốt hơn tôi muốn nói, bởi vì nó được thử nghiệm trong sự cô lập ... Mục đích duy nhất của thử nghiệm đó là để xem các vấn đề hiệu suất tiềm năng khi sử dụng eval. Về mẫu chữ: cảm ơn vì đã chỉ ra điều đó một lần nữa. Tôi đang sử dụng Babel để dịch mã của mình nhưng chức năng của tôi vẫn không hoạt động rõ ràng 😐
Andrea Puddu

3

Hiện tại tôi không thể nhận xét về các câu trả lời hiện có vì vậy tôi không thể nhận xét trực tiếp về phản hồi tuyệt vời của Bryan Raynor. Vì vậy, phản ứng này sẽ cập nhật câu trả lời của anh ấy với một sự điều chỉnh nhẹ.

Nói tóm lại, chức năng của anh ta không thực sự lưu trữ chức năng đã tạo, do đó, nó sẽ luôn tạo lại, bất kể nó có nhìn thấy mẫu trước đó hay không. Đây là mã sửa:

    /**
     * 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, giải pháp hoạt động rất tốt, nhưng khi tôi sử dụng nó trong React Native (chế độ xây dựng), nó sẽ báo lỗi: Ký tự không hợp lệ '`' , mặc dù nó hoạt động khi tôi chạy nó trong chế độ gỡ lỗi.

Vì vậy, tôi đã viết ra giải pháp của riêng tôi bằng cách sử dụng 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)

Bản trình diễn: https://es6console.com/j31pqx1p/

LƯU Ý: Vì tôi không biết nguyên nhân cốt lõi của vấn đề, tôi đã đặt một vé trong repo gốc phản ứng, https://github.com/facebook/react-native/issues/14107 , để một khi họ có thể sửa chữa / hướng dẫn tôi về cùng :)


cái này không hỗ trợ các mẫu chứa ký tự back-tick. Tuy nhiên, thay vì cố gắng phát minh ra một mô hình khuôn mẫu, có lẽ bạn nên sử dụng ria mép hoặc tương tự . tùy thuộc vào mức độ phức tạp của các mẫu của bạn, đây là một cách tiếp cận mạnh mẽ mà không xem xét các trường hợp cạnh - khóa có thể chứa một mẫu biểu thức chính quy đặc biệt.
SliverNinja - MSFT

2

Vẫn năng động nhưng dường như được kiểm soát nhiều hơn là chỉ sử dụng một eval khỏa thân:

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

Giải pháp này hoạt động mà không cần 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"

Lưu ý: hàm Functiontạo luôn được tạo trong phạm vi toàn cục, điều này có khả năng khiến các biến toàn cục bị ghi đè bởi mẫu, ví dụ:render("hello ${ someGlobalVar = 'some new value' }", {name:'mo'});


0

Vì chúng tôi đang phát minh lại bánh xe trên một thứ gì đó sẽ là một tính năng đáng yêu trong javascript.

Tôi sử dụng eval(), không an toàn, nhưng javascript không an toàn. Tôi dễ dàng thừa nhận rằng tôi không xuất sắc với javascript, nhưng tôi có nhu cầu và tôi cần một câu trả lời nên tôi đã làm một.

Tôi đã chọn cách điệu các biến của mình bằng một @thay vì một $, đặc biệt vì tôi muốn sử dụng tính năng đa dòng của chữ mà không đánh giá cho đến khi nó sẵn sàng. Vì vậy, cú pháp biến là@{OptionalObject.OptionalObjectN.VARIABLE_NAME}

Tôi không phải là chuyên gia javascript, vì vậy tôi sẵn sàng tư vấn về cải tiến nhưng ...

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);
}

Một cách thực hiện rất đơn giản sau

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);
}

Trong thực hiện thực tế của tôi, tôi chọn sử dụng @{{variable}}. Thêm một bộ niềng răng. Vô lý không thể gặp phải điều đó bất ngờ. Regex cho điều đó sẽ trông giống như/\@\{\{(.*?)(?!\@\{\{)\}\}/g

Để dễ đọc hơn

\@\{\{    # 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.

Nếu bạn không có kinh nghiệm với regex, một quy tắc khá an toàn là thoát mọi ký tự không phải là chữ và số, và đừng bao giờ thoát khỏi các chữ cái vì nhiều chữ cái thoát có ý nghĩa đặc biệt đối với hầu như tất cả các hương vị của regex.


0

Bạn nên thử mô-đun JS nhỏ này, của Andrea Giammarchi, từ 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);
};

Bản demo (tất cả các bài kiểm tra sau đây đều đúng):

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

Tôi đã thực hiện giải pháp của riêng mình khi thực hiện một loại với mô tả là hàm

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

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

và làm như vậy:

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

0

Thay vì sử dụng eval tốt hơn là sử dụng để regex

Eval không được khuyến khích và không được khuyến khích, vì vậy vui lòng không sử dụng nó ( mdn eval ).

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

let response = a.replace(/\${\w+}/ ,b);
conssole.log(response);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.