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 Function
và các phương tiện tạo mã động khác?
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 Function
và các phương tiện tạo mã động khác?
Câu trả lời:
Vì chuỗi mẫu của bạn phải được tham chiếu đến b
biế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 eval
nó khá đơn giản:
let tpl = eval('`'+a+'`');
a
chuỗ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à eval
ngă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.
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
.
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ị.
ReferenceError: _ is not defined
. Có phải mã không phải ES6 nhưng lodash
cụ thể, hay ...?
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ù. :
template is not a function
.
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.
/**
* 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.
var test = generateTemplateString('/api/${param1}/${param2}/')
console.log(test({param1: 'bar', param2: 'foo'}))
trả lại mẫu/api/bar//
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
${}
ký tự. Hãy thử:/(?!\${)([^{}]*)(?=})/g
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));
eval
bởi vì nó chỉ hoạt động với một name
biến toàn cục
var template = function() { var name = "John Smith"; var message = "Hello, my name is ${name}"; this.local = new Function('return
'+ tin nhắn +';')();}
new Function
không có quyền truy cập var name
vào template
chức năng.
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?
parts: ["Hello, ", "! Are you ", " years old?"]
args: ["name", "age"]
obj
tê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]
String.raw(...)
và trả lại kết quả..replace()
liên tục không?
.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ả ...
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 ${
và }
).
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)
/\$\{(.*?)(?!\$\{)\}/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()
.
eval
khiế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.
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
)
);
}
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 eval
hoặc tương đương Function
khô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 -
eval('`' + taggedURL + '`')
đơn giản là không làm việc.
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 😐
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;
})();
@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 :)
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))
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 Function
tạ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'});
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.
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});
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');
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);