Helpex - Trao đổi & giúp đỡ Đăng nhập
55

Tôi đang chơi với tính năng ES6 Template Literals mới và điều đầu tiên tôi nghĩ đến là String.formatJavaScript, vì vậy tôi bắt đầu triển khai một nguyên mẫu:

String.prototype.format = function() {
  var self = this;
  arguments.forEach(function(val,idx) {
    self["p"+idx] = val;
  });
  return this.toString();
};
console.log(`Hello, ${p0}. This is a ${p1}`.format("world", "test"));

ES6Fiddle

Tuy nhiên, Template Literal được đánh giá trước khi nó được chuyển đến phương pháp nguyên mẫu của tôi. Có cách nào tôi có thể viết đoạn mã trên để trì hoãn kết quả cho đến khi tôi đã tạo động các phần tử không?

55 hữu ích 5 bình luận 10k xem chia sẻ
65

Tôi có thể thấy ba cách để giải quyết vấn đề này:

  • Sử dụng các chuỗi mẫu giống như chúng được thiết kế để sử dụng, không có bất kỳ formatchức năng nào :

    console.log(`Hello, ${"world"}. This is a ${"test"}`);
    // might make more sense with variables:
    var p0 = "world", p1 = "test";
    console.log(`Hello, ${p0}. This is a ${p1}`);
    

    hoặc thậm chí các thông số chức năng để trì hoãn đánh giá thực tế:

    const welcome = (p0, p1) => `Hello, ${p0}. This is a ${p1}`;
    console.log(welcome("world", "test"));
    
  • Không sử dụng chuỗi mẫu, mà là một chuỗi thuần túy:

    String.prototype.format = function() {
        var args = arguments;
        return this.replace(/\$\{p(\d)\}/g, function(match, id) {
            return args[id];
        });
    };
    console.log("Hello, ${p0}. This is a ${p1}".format("world", "test"));
    
  • Sử dụng một mẫu được gắn thẻ theo nghĩa đen. Lưu ý rằng các thay thế sẽ vẫn được đánh giá mà không bị trình xử lý đánh chặn, vì vậy bạn không thể sử dụng các số nhận dạng như p0mà không có một biến được đặt tên như vậy. Hành vi này có thể thay đổi nếu một đề xuất cú pháp nội dung thay thế khác được chấp nhận (Cập nhật: không phải vậy).

    function formatter(literals, ...substitutions) {
        return {
            format: function() {
                var out = [];
                for(var i=0, k=0; i < literals.length; i++) {
                    out[k++] = literals[i];
                    out[k++] = arguments[substitutions[i]];
                }
                out[k] = literals[i];
                return out.join("");
            }
        };
    }
    console.log(formatter`Hello, ${0}. This is a ${1}`.format("world", "test"));
    // Notice the number literals: ^               ^
    
65 hữu ích 4 bình luận chia sẻ
5

Mở rộng câu trả lời của @Bergi, sức mạnh của các chuỗi mẫu được gắn thẻ sẽ tự bộc lộ khi bạn nhận ra rằng kết quả là bạn có thể trả về bất kỳ thứ gì, không chỉ các chuỗi đơn thuần. Trong ví dụ của anh ấy, thẻ xây dựng và trả về một đối tượng có thuộc tính hàm và thuộc tính đóng format.

Trong cách tiếp cận yêu thích của tôi, tôi tự trả về một giá trị hàm mà bạn có thể gọi sau này và chuyển các tham số mới để điền vào mẫu. Như thế này:

function fmt([fisrt, ...rest], ...tags) {
  return values => rest.reduce((acc, curr, i) => {
    return acc + values[tags[i]] + curr;
  }, fisrt);
}

Sau đó, bạn xây dựng các mẫu của mình và trì hoãn các thay thế:

> fmt`Test with ${0}, ${1}, ${2} and ${0} again`(['A', 'B', 'C']);
// 'Test with A, B, C and A again'
> template = fmt`Test with ${'foo'}, ${'bar'}, ${'baz'} and ${'foo'} again`
> template({ foo:'FOO', bar:'BAR' })
// 'Test with FOO, BAR, undefined and FOO again'

Một tùy chọn khác, gần hơn với những gì bạn đã viết, sẽ là trả về một đối tượng được mở rộng từ một chuỗi, để gõ vịt ra khỏi hộp và giao diện tôn trọng. Tiện ích mở rộng String.prototypesẽ không hoạt động vì bạn cần đóng thẻ mẫu để giải quyết các thông số sau này.

class FormatString extends String {
  // Some other custom extensions that don't need the template closure
}

function fmt([fisrt, ...rest], ...tags) {
  const str = new FormatString(rest.reduce((acc, curr, i) => `${acc}\${${tags[i]}}${curr}`, fisrt));
  str.format = values => rest.reduce((acc, curr, i) => {
    return acc + values[tags[i]] + curr;
  }, fisrt);
  return str;
}

Sau đó, trong call-site:

> console.log(fmt`Hello, ${0}. This is a ${1}.`.format(["world", "test"]));
// Hello, world. This is a test.
> template = fmt`Hello, ${'foo'}. This is a ${'bar'}.`
> console.log(template)
// { [String: 'Hello, ${foo}. This is a ${bar}.'] format: [Function] }
> console.log(template.format({ foo: true, bar: null }))
// Hello, true. This is a null.

Bạn có thể tham khảo thêm thông tin và ứng dụng trong câu trả lời khác này .

5 hữu ích 1 bình luận chia sẻ
3

AFAIS, tính năng hữu ích "thực hiện hoãn lại các mẫu chuỗi" vẫn chưa khả dụng. Tuy nhiên, sử dụng lambda là một giải pháp diễn đạt, dễ đọc và ngắn gọn:

var greetingTmpl = (...p)=>`Hello, ${p[0]}. This is a ${p[1]}`;

console.log( greetingTmpl("world","test") );
console.log( greetingTmpl("@CodingIntrigue","try") );
3 hữu ích 0 bình luận chia sẻ
1

Tôi cũng thích ý tưởng về String.formathàm và có thể xác định rõ ràng các biến để phân giải.

Đây là những gì tôi đã nghĩ ra ... về cơ bản là một String.replacephương pháp có deepObjecttra cứu.

const isUndefined = o => typeof o === 'undefined'

const nvl = (o, valueIfUndefined) => isUndefined(o) ? valueIfUndefined : o

// gets a deep value from an object, given a 'path'.
const getDeepValue = (obj, path) =>
  path
    .replace(/\[|\]\.?/g, '.')
    .split('.')
    .filter(s => s)
    .reduce((acc, val) => acc && acc[val], obj)

// given a string, resolves all template variables.
const resolveTemplate = (str, variables) => {
  return str.replace(/\$\{([^\}]+)\}/g, (m, g1) =>
            nvl(getDeepValue(variables, g1), m))
}

// add a 'format' method to the String prototype.
String.prototype.format = function(variables) {
  return resolveTemplate(this, variables)
}

// setup variables for resolution...
var variables = {}
variables['top level'] = 'Foo'
variables['deep object'] = {text:'Bar'}
var aGlobalVariable = 'Dog'

// ==> Foo Bar <==
console.log('==> ${top level} ${deep object.text} <=='.format(variables))

// ==> Dog Dog <==
console.log('==> ${aGlobalVariable} ${aGlobalVariable} <=='.format(this))

// ==> ${not an object.text} <==
console.log('==> ${not an object.text} <=='.format(variables))
Mở rộng đoạn mã

Ngoài ra, nếu bạn muốn nhiều hơn chỉ là độ phân giải thay đổi (ví dụ: hành vi của các ký tự mẫu), bạn có thể sử dụng cách sau.

NB eval được coi là 'ác' - hãy cân nhắc sử dụng một giải safe-evalpháp thay thế.

// evalutes with a provided 'this' context.
const evalWithContext = (string, context) => function(s){
    return eval(s);
  }.call(context, string)

// given a string, resolves all template variables.
const resolveTemplate = function(str, variables) {
  return str.replace(/\$\{([^\}]+)\}/g, (m, g1) => evalWithContext(g1, variables))
}

// add a 'format' method to the String prototype.
String.prototype.format = function(variables) {
  return resolveTemplate(this, variables)
}

// ==> 5Foobar <==
console.log('==> ${1 + 4 + this.someVal} <=='.format({someVal: 'Foobar'}))
Mở rộng đoạn mã

1 hữu ích 0 bình luận chia sẻ
1

Bạn có thể đưa các giá trị vào chuỗi bằng cách sử dụng hàm dưới đây

let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);

Hiển thị đoạn mã

1 hữu ích 0 bình luận chia sẻ
0

Tôi đã đăng câu trả lời cho một câu hỏi tương tự đưa ra hai cách tiếp cận mà việc thực thi nghĩa đen của mẫu bị trì hoãn. Khi ký tự mẫu nằm trong một hàm, ký tự mẫu chỉ được đánh giá khi hàm được gọi và nó được đánh giá bằng cách sử dụng phạm vi của hàm.

https://stackoverflow.com/a/49539260/188963

0 hữu ích 0 bình luận chia sẻ
0

Mặc dù câu hỏi này đã được trả lời, ở đây tôi có một cách triển khai đơn giản mà tôi sử dụng khi tải các tệp cấu hình (mã là kiểu chữ, nhưng rất dễ chuyển đổi thành JS, chỉ cần loại bỏ các kiểu đánh máy):

/**
 * This approach has many limitations:
 *   - it does not accept variable names with numbers or other symbols (relatively easy to fix)
 *   - it does not accept arbitrary expressions (quite difficult to fix)
 */
function deferredTemplateLiteral(template: string, env: { [key: string]: string | undefined }): string {
  const varsMatcher = /\${([a-zA-Z_]+)}/
  const globalVarsmatcher = /\${[a-zA-Z_]+}/g

  const varMatches: string[] = template.match(globalVarsmatcher) ?? []
  const templateVarNames = varMatches.map(v => v.match(varsMatcher)?.[1] ?? '')
  const templateValues: (string | undefined)[] = templateVarNames.map(v => env[v])

  const templateInterpolator = new Function(...[...templateVarNames, `return \`${template}\`;`])

  return templateInterpolator(...templateValues)
}

// Usage:
deferredTemplateLiteral("hello ${thing}", {thing: "world"}) === "hello world"

Mặc dù có thể làm cho công cụ này trở nên mạnh mẽ và linh hoạt hơn, nhưng nó mang lại quá nhiều phức tạp và rủi ro mà không mang lại nhiều lợi ích.

Đây là liên kết đến ý chính: https://gist.github.com/castarco/94c5385539cf4d7104cc4d3513c14f55

0 hữu ích 0 bình luận chia sẻ
0

(xem câu trả lời rất giống của @ Bergi ở trên)

function interpolate(strings, ...positions) {
  var errors = positions.filter(pos=>~~pos!==pos);
  if (errors.length) {
    throw "Invalid Interpolation Positions: " + errors.join(', ');
  }
  return function $(...vals) {
    var output = '';
    for (let i = 0; i < positions.length; i ++) {
      output += (strings[i] || '') + (vals[positions[i] - 1] || '');
    }
    output += strings[strings.length - 1];
    return output;
  };
}

var iString = interpolate`This is ${1}, which is pretty ${2} and ${3}. Just to reiterate, ${1} is ${2}! (nothing ${0} ${100} here)`;
// Sets iString to an interpolation function

console.log(iString('interpolation', 'cool', 'useful', 'extra'));
// Substitutes the values into the iString and returns:
//   'This is interpolation, which is pretty cool and useful.
//   Just to reiterate, interpolation is cool! (nothing  here)'
Mở rộng đoạn mã

Sự khác biệt chính giữa câu trả lời này và câu trả lời của @ Bergi là cách xử lý lỗi (âm thầm so với không).

Sẽ đủ dễ dàng để mở rộng ý tưởng này thành một cú pháp chấp nhận các đối số được đặt tên:

interpolate`This is ${'foo'}, which is pretty ${'bar'}.`({foo: 'interpolation', bar: 'cool'});

https://github.com/spikesagal/es6interpolate/blob/main/src/interpolate.js

0 hữu ích 0 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ javascript ecmascript-6 template-literals , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading