Nunjucks模板语法完全指南
本文深入探讨了Nunjucks模板引擎的核心功能,包括变量输出和表达式语法、过滤器系统、控制结构(if条件判断和for循环)以及模板继承和block块系统。文章详细解析了每种功能的语法规则、使用方法和最佳实践,帮助开发者掌握Nunjucks的强大功能,创建灵活高效的模板。
变量输出和表达式语法详解
Nunjucks作为一款功能强大的JavaScript模板引擎,其变量输出和表达式语法是整个模板系统的核心基础。本节将深入探讨Nunjucks中变量输出的各种方式、表达式语法规则以及最佳实践。
基础变量输出
在Nunjucks中,最基本的变量输出使用双花括号语法:
{{ username }}
{{ user.profile.name }}
{{ items[0].title }}
这种语法会从模板上下文中查找对应的变量值并输出。如果变量不存在或值为null/undefined,则不会输出任何内容。
属性访问语法
Nunjucks支持两种属性访问方式,与JavaScript保持一致:
{# 点表示法 - 最常用 #}
{{ user.name }}
{{ article.author.email }}
{# 方括号表示法 - 适用于动态属性名 #}
{{ user["name"] }}
{{ article["author"]["email"] }}
{{ items[index].title }}
表达式语法详解
Nunjucks支持丰富的表达式语法,包括算术运算、比较运算、逻辑运算等:
算术运算
{{ 5 + 3 }} {# 输出: 8 #}
{{ 10 - 4 }} {# 输出: 6 #}
{{ 6 * 7 }} {# 输出: 42 #}
{{ 15 / 3 }} {# 输出: 5 #}
{{ 17 % 5 }} {# 输出: 2 #}
比较运算
{{ 5 == 5 }} {# 输出: true #}
{{ 5 != 3 }} {# 输出: true #}
{{ 10 > 5 }} {# 输出: true #}
{{ 5 < 10 }} {# 输出: true #}
{{ 5 >= 5 }} {# 输出: true #}
{{ 5 <= 10 }} {# 输出: true #}
逻辑运算
{{ true and false }} {# 输出: false #}
{{ true or false }} {# 输出: true #}
{{ not false }} {# 输出: true #}
复杂表达式示例
Nunjucks支持复杂的嵌套表达式:
{{ (user.age >= 18) and (user.country == "US") }}
{{ (items.length > 0) and (items[0].price > 100) }}
{{ (score * 100) / totalQuestions }}
表达式执行流程
变量查找机制
Nunjucks的变量查找遵循特定的作用域链:
| 查找顺序 | 作用域类型 | 描述 |
|---|---|---|
| 1 | 局部变量 | 当前块级作用域内定义的变量 |
| 2 | 模板参数 | 渲染时传入的上下文变量 |
| 3 | 全局变量 | 通过addGlobal添加的全局变量 |
| 4 | 过滤器参数 | 过滤器函数中的参数 |
安全输出处理
Nunjucks默认启用自动转义功能,防止XSS攻击:
{# 自动转义HTML特殊字符 #}
{{ userInput }} {# 安全输出 #}
{# 需要输出原始HTML时使用safe过滤器 #}
{{ htmlContent | safe }}
空值处理策略
Nunjucks对空值的处理非常智能:
{# 以下情况都不会输出内容 #}
{{ undefinedVar }}
{{ nullVar }}
{{ nonexistent.property }}
{{ emptyArray[5] }}
{# 但零值和空字符串会正常输出 #}
{{ 0 }} {# 输出: 0 #}
{{ "" }} {# 输出: (空字符串) #}
{{ false }} {# 输出: false #}
表达式性能优化
为了提高模板渲染性能,建议:
- 避免深层嵌套属性访问:
{{ user.profile.contact.address.city }}比{{ city }}慢 - 缓存重复使用的表达式:使用
set标签缓存复杂表达式结果 - 减少不必要的计算:在JavaScript中预处理数据,而不是在模板中进行复杂计算
实际应用示例
{# 用户信息展示 #}
<div class="user-card">
<h2>{{ user.firstName }} {{ user.lastName }}</h2>
<p>年龄: {{ user.age }}岁</p>
<p>会员状态: {{ user.isVIP ? 'VIP会员' : '普通会员' }}</p>
<p>积分: {{ user.points | default(0) }}</p>
</div>
{# 条件价格显示 #}
<span class="price">
{{ product.onSale ? product.salePrice : product.regularPrice }}
</span>
{# 复杂表达式计算 #}
{% set discount = (originalPrice - salePrice) / originalPrice * 100 %}
<p>折扣率: {{ discount | round }}%</p>
通过掌握Nunjucks的变量输出和表达式语法,开发者可以创建出既灵活又高效的模板,为Web应用提供强大的视图渲染能力。记住合理运用表达式语法,既能保持模板的简洁性,又能确保应用的性能表现。
过滤器系统:内置过滤器与自定义过滤器
Nunjucks的过滤器系统是其模板引擎的核心功能之一,提供了强大的数据处理和格式化能力。过滤器通过管道符号(|)应用于变量,能够对数据进行各种转换、格式化和处理操作。
内置过滤器概览
Nunjucks提供了丰富的内置过滤器,涵盖了字符串处理、数组操作、数据格式化等多个方面。以下是一些常用的内置过滤器:
| 过滤器名称 | 功能描述 | 示例用法 |
|---|---|---|
capitalize | 首字母大写 | {{ "hello" \| capitalize }} → "Hello" |
lower | 转换为小写 | {{ "HELLO" \| lower }} → "hello" |
upper | 转换为大写 | {{ "hello" \| upper }} → "HELLO" |
title | 每个单词首字母大写 | {{ "hello world" \| title }} → "Hello World" |
trim | 去除首尾空白字符 | {{ " hello " \| trim }} → "hello" |
default | 提供默认值 | {{ undefined \| default("N/A") }} → "N/A" |
length | 获取长度 | {{ [1,2,3] \| length }} → 3 |
first | 获取第一个元素 | {{ [1,2,3] \| first }} → 1 |
last | 获取最后一个元素 | {{ [1,2,3] \| last }} → 3 |
join | 数组元素连接 | {{ [1,2,3] \| join(",") }} → "1,2,3" |
sort | 数组排序 | {{ [3,1,2] \| sort }} → [1,2,3] |
escape | HTML转义 | {{ "<div>" \| escape }} → "<div>" |
safe | 标记为安全HTML | {{ "<div>safe</div>" \| safe }} → 渲染为div元素 |
过滤器的工作原理
Nunjucks过滤器的执行流程可以通过以下序列图来理解:
字符串处理过滤器详解
字符串处理是过滤器最常见的应用场景。Nunjucks提供了多种字符串操作过滤器:
// 字符串大小写转换
{{ "hello world" | capitalize }} // → "Hello world"
{{ "Hello World" | lower }} // → "hello world"
{{ "hello world" | upper }} // → "HELLO WORLD"
// 字符串格式化
{{ " hello " | trim }} // → "hello"
{{ "hello\nworld" | nl2br }} // → "hello<br />\nworld"
// 字符串截取和填充
{{ "hello" | truncate(3) }} // → "hel..."
{{ "hi" | center(10) }} // → " hi "
数组和对象操作过滤器
对于集合数据的处理,Nunjucks提供了强大的数组和对象操作过滤器:
// 数组基本操作
{{ [3, 1, 2] | sort }} // → [1, 2, 3]
{{ [1, 2, 3] | reverse }} // → [3, 2, 1]
{{ [1, 2, 3, 4] | batch(2) }} // → [[1, 2], [3, 4]]
// 对象操作
{{ {b: 2, a: 1} | dictsort }} // → [["a", 1], ["b", 2]]
{{ {name: "John", age: 30} | list }} // → [{key: "name", value: "John"}, {key: "age", value: 30}]
// 条件筛选
{{ [1, 2, 3, 4] | select("odd") }} // → [1, 3]
{{ users | selectattr("active") }} // → 筛选出active为true的用户
自定义过滤器开发
Nunjucks允许开发者创建自定义过滤器来扩展模板功能。自定义过滤器的注册方法如下:
const nunjucks = require('nunjucks');
const env = nunjucks.configure('views');
// 简单的自定义过滤器
env.addFilter('shorten', function(str, count) {
return str.slice(0, count || 5);
});
// 带上下文的自定义过滤器
env.addFilter('greet', function(name) {
return `Hello, ${name}! Today is ${this.ctx.date}`;
});
// 异步自定义过滤器
env.addFilter('fetchData', async function(url) {
const response = await fetch(url);
return await response.json();
}, true);
过滤器链式调用
Nunjucks支持过滤器的链式调用,允许对同一个值进行多次处理:
// 多重过滤处理
{{ " HELLO WORLD " | trim | lower | capitalize }}
// → "Hello world"
{{ products | sort | reverse | first }}
// → 获取排序后倒序的第一个产品
{{ user.input | escape | nl2br }}
// → 先转义HTML再转换换行符
过滤器参数传递
过滤器支持传递多个参数,参数之间用逗号分隔:
// 带参数的过滤器调用
{{ "hello world" | truncate(5, true, "...") }}
// → "hello..."
{{ [1, 2, 3, 4, 5] | batch(2, "empty") }}
// → [[1, 2], [3, 4], [5, "empty"]]
{{ items | join(", ", "name") }}
// → 提取name属性并用逗号连接
安全相关的过滤器
在处理用户输入时,安全过滤器至关重要:
// HTML转义保护
{{ userContent | escape }} // 防止XSS攻击
{{ userContent | forceescape }} // 强制转义,即使已经是SafeString
// 安全内容标记
{{ trustedHTML | safe }} // 标记为安全HTML内容
高级过滤器模式
对于复杂场景,可以创建更高级的过滤器:
// 条件过滤器
env.addFilter('iff', function(value, truthy, falsy) {
return value ? truthy : falsy;
});
// 数学运算过滤器
env.addFilter('add', function(a, b) {
return a + b;
});
env.addFilter('multiply', function(a, b) {
return a * b;
});
// 日期格式化过滤器
env.addFilter('formatDate', function(date, format) {
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).format(new Date(date));
});
过滤器性能优化
在使用过滤器时,需要注意性能考虑:
最佳实践建议
- 优先使用内置过滤器:内置过滤器经过优化且功能稳定
- 避免过度链式调用:过多的过滤器链会影响性能
- 异步过滤器谨慎使用:在模板中避免频繁的异步操作
- 安全性第一:处理用户输入时务必使用escape过滤器
- 自定义过滤器测试:确保自定义过滤器的边界情况处理正确
通过合理使用Nunjucks的过滤器系统,可以极大地增强模板的表达能力和数据处理能力,同时保持代码的简洁性和可维护性。
控制结构:if条件判断和for循环
Nunjucks作为一款功能强大的JavaScript模板引擎,提供了丰富的控制结构来处理复杂的逻辑和数据处理需求。其中,if条件判断和for循环是最核心且常用的两种控制结构,它们让模板能够根据数据状态动态生成内容。
if条件判断
if语句在Nunjucks中的行为与JavaScript中的if语句完全一致,允许你根据条件表达式的结果选择性显示内容。
基本语法
{% if condition %}
<!-- 条件为真时显示的内容 -->
{% endif %}
当condition表达式结果为真值时(非false、null、undefined、0、NaN或空字符串),块内内容会被渲染。
多条件分支
Nunjucks支持完整的条件分支结构,包括elif(或elseif)和else:
{% if user.role === 'admin' %}
<div class="admin-panel">管理员控制台</div>
{% elif user.role === 'editor' %}
<div class="editor-tools">编辑工具</div>
{% else %}
<div class="user-welcome">欢迎,普通用户!</div>
{% endif %}
逻辑运算符
支持and、or等逻辑运算符来组合多个条件:
{% if user.isLoggedIn and user.hasPermission('write') %}
<button>发布文章</button>
{% endif %}
{% if product.stock === 0 or product.discontinued %}
<span class="out-of-stock">缺货</span>
{% endif %}
复杂条件表达式
Nunjucks支持复杂的条件表达式,包括比较运算符和函数调用:
{% if items.length > 0 and items[0].price < 100 %}
<div class="affordable">实惠商品</div>
{% endif %}
{% if date | isFuture %}
<span class="upcoming">即将到来</span>
{% endif %}
for循环迭代
for循环是处理集合数据的强大工具,支持数组、对象、Map、Set等多种数据结构。
数组迭代
{% for product in products %}
<div class="product-item">
<h3>{{ product.name }}</h3>
<p>价格: {{ product.price | currency }}</p>
</div>
{% endfor %}
对象迭代
迭代对象时可以使用键值对形式:
{% for key, value in settings %}
<div class="setting">
<label>{{ key }}:</label>
<span>{{ value }}</span>
</div>
{% endfor %}
空集合处理
使用else子句处理空集合情况:
{% for item in cartItems %}
<div class="cart-item">{{ item.name }} - {{ item.quantity }}</div>
{% else %}
<div class="empty-cart">购物车为空</div>
{% endfor %}
循环控制变量
在循环内部,可以通过loop对象访问循环状态信息:
| 变量名 | 描述 | 示例值 |
|---|---|---|
loop.index | 当前迭代序号(从1开始) | 1, 2, 3... |
loop.index0 | 当前迭代序号(从0开始) | 0, 1, 2... |
loop.first | 是否为第一次迭代 | true/false |
loop.last | 是否为最后一次迭代 | true/false |
loop.length | 集合的总长度 | 5 |
{% for user in users %}
<tr class="{% if loop.first %}first-row{% endif %} {% if loop.last %}last-row{% endif %}">
<td>{{ loop.index }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
</tr>
{% endfor %}
多维数组解构
Nunjucks支持ES6风格的多维数组解构:
{% for x, y, z in coordinates %}
<div>坐标: ({{ x }}, {{ y }}, {{ z }})</div>
{% endfor %}
高级用法示例
条件循环组合
{% for item in items %}
{% if item.visible %}
<div class="item {% if loop.index % 2 == 0 %}even{% else %}odd{% endif %}">
<h4>{{ item.title }}</h4>
{% if item.description %}
<p>{{ item.description | truncate(100) }}</p>
{% endif %}
</div>
{% endif %}
{% else %}
<div class="no-items">暂无项目</div>
{% endfor %}
嵌套循环
{% for category in categories %}
<h3>{{ category.name }}</h3>
<ul>
{% for product in category.products %}
<li>{{ product.name }} - {{ product.price }}</li>
{% endfor %}
</ul>
{% endfor %}
循环范围控制
虽然Nunjucks没有内置的break或continue,但可以通过条件判断模拟:
{% for item in items %}
{% if loop.index > 5 %}{% break %}{% endif %}
<div>{{ item }}</div>
{% endfor %}
性能优化建议
- 避免深层嵌套:过深的嵌套会影响模板渲染性能
- 预处理数据:在JavaScript中预处理复杂数据,减少模板中的逻辑
- 使用过滤器:将复杂逻辑封装为过滤器,提高模板可读性
- 缓存结果:对不经常变化的数据使用缓存机制
通过合理运用if条件判断和for循环,你可以构建出既灵活又高效的模板结构,满足各种复杂的业务场景需求。这些控制结构是Nunjucks模板强大表达能力的核心所在。
模板继承和block块系统
Nunjucks的模板继承系统是其最强大的功能之一,它允许你创建可重用的模板结构,通过block块机制实现内容的灵活替换和扩展。这套系统借鉴了Jinja2的设计理念,为构建复杂的Web应用提供了优雅的解决方案。
继承机制的核心概念
模板继承基于两个核心标签:{% extends %} 和 {% block %}。extends标签用于指定父模板,而block标签定义了可被覆盖的内容区域。
{# base.html - 父模板 #}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}默认标题{% endblock %}</title>
</head>
<body>
<header>{% block header %}网站头部{% endblock %}</header>
<main>{% block content %}{% endblock %}</main>
<footer>{% block footer %}© 2023 我的网站{% endblock %}</footer>
</body>
</html>
Block块的定义和使用
Block块是模板继承的基本单元,每个block都有一个唯一的名称,子模板可以通过重新定义同名的block来覆盖父模板中的内容。
{# child.html - 子模板 #}
{% extends "base.html" %}
{% block title %}个性化标题 - 我的页面{% endblock %}
{% block content %}
<h1>欢迎来到我的页面</h1>
<p>这是自定义的内容区域</p>
{% endblock %}
Super函数:继承与扩展
Nunjucks提供了super()函数,允许子模板在覆盖父模板内容的同时,保留并扩展父模板的原始内容。
{% block header %}
{{ super() }}
<nav>
<a href="/">首页</a>
<a href="/about">关于</a>
<a href="/contact">联系</a>
</nav>
{% endblock %}
多级继承体系
Nunjucks支持多级模板继承,你可以构建复杂的模板层次结构:
Block块的嵌套和作用域
Block块可以嵌套使用,并且具有独立的作用域。在block内部定义的变量不会影响到外部的模板上下文。
{% block sidebar %}
{% set sidebarItems = ['最新文章', '热门标签', '归档'] %}
<div class="sidebar">
<ul>
{% for item in sidebarItems %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</div>
{% endblock %}
动态模板继承
Nunjucks支持动态继承,你可以根据条件选择不同的父模板:
{% if user.isAdmin %}
{% extends "admin-layout.njk" %}
{% else %}
{% extends "user-layout.njk" %}
{% endif %}
{% block content %}
{# 内容会根据不同的布局模板进行渲染 #}
{% endblock %}
Block块的默认内容
父模板中的block可以包含默认内容,当子模板没有覆盖该block时,默认内容会被渲染:
{% block meta_description %}
<meta name="description" content="这是一个默认的网站描述">
{% endblock %}
最佳实践和模式
为了有效地使用模板继承系统,推荐以下模式:
| 模式类型 | 描述 | 示例 |
|---|---|---|
| 基础布局模式 | 创建包含基本HTML结构的模板 | 包含<html>, <head>, <body> |
| 区域划分模式 | 将页面划分为逻辑区域 | header, main, sidebar, footer |
| 组件复用模式 | 创建可重用的UI组件 | navigation, card, modal |
| 条件继承模式 | 根据条件选择不同的父模板 | 用户角色、设备类型 |
错误处理和调试
当使用模板继承时,需要注意常见的错误情况:
- 循环继承:避免模板之间形成循环引用
- 未定义block:确保所有使用的block都在父模板中有定义
- 作用域冲突:注意变量在block内部和外部的可见性
{# 错误的循环继承示例 #}
{% extends "child.njk" %} {# 如果child.njk又extends这个模板就形成循环 #}
性能考虑
模板继承系统在编译阶段会解析所有的extends和block关系,生成优化的渲染代码。多级继承不会带来显著的性能开销,因为Nunjucks会预先编译整个继承链。
通过合理使用模板继承和block块系统,你可以创建出结构清晰、易于维护的模板架构,大大提高前端开发的效率和代码的可重用性。
总结
Nunjucks作为一款功能强大的JavaScript模板引擎,提供了完整的模板解决方案。通过变量输出和表达式语法,开发者可以灵活处理数据;过滤器系统支持丰富的数据转换和格式化操作;控制结构(if条件判断和for循环)实现了复杂的逻辑处理;模板继承和block块系统则提供了优雅的代码复用机制。掌握这些核心功能,能够显著提升前端开发效率和代码质量,构建出结构清晰、易于维护的Web应用。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



