Liquid 性能优化
优化 Liquid 模板性能对于提供良好的用户体验至关重要。本指南将详细介绍各种性能优化技巧和最佳实践。
性能监控和分析
1. 性能指标测量
<!-- 渲染时间监控 -->
{% assign start_time = 'now' | date: '%s' | plus: 0 %}
<!-- 主要内容渲染 -->
<div class="main-content">
{% for product in collection.products limit: 20 %}
{% render 'product-card', product: product %}
{% endfor %}
</div>
{% assign end_time = 'now' | date: '%s' | plus: 0 %}
{% assign render_duration = end_time | minus: start_time %}
{% if settings.show_debug_info %}
<div class="debug-timing">
<p>页面渲染耗时: {{ render_duration }}秒</p>
<p>处理商品数量: {{ collection.products.size }}</p>
<p>平均每个商品: {{ render_duration | divided_by: collection.products.size | round: 3 }}秒</p>
</div>
{% endif %}
2. 对象访问统计
<!-- 统计复杂操作次数 -->
{% assign filter_operations = 0 %}
{% assign sort_operations = 0 %}
{% assign loop_iterations = 0 %}
<!-- 监控性能关键指标 -->
{% if settings.performance_monitoring %}
<script>
window.performanceMetrics = {
totalProducts: {{ collection.products.size }},
filteredProducts: {{ collection.products | where: 'available', true | size }},
renderStartTime: performance.now()
};
</script>
{% endif %}
数据预处理优化
1. 减少重复计算
<!-- 不好的做法 - 重复计算 -->
{% for product in collection.products %}
{% if collection.products.size > 20 %}
<!-- 每次循环都计算 size -->
{% endif %}
{% if collection.products | where: 'available', true | size > 10 %}
<!-- 每次循环都执行复杂过滤 -->
{% endif %}
{% endfor %}
<!-- 好的做法 - 预计算 -->
{% assign total_products = collection.products.size %}
{% assign available_products = collection.products | where: 'available', true %}
{% assign available_count = available_products.size %}
{% assign large_collection = total_products > 20 %}
{% assign has_sufficient_stock = available_count > 10 %}
{% for product in collection.products %}
{% if large_collection %}
<!-- 使用预计算的布尔值 -->
{% endif %}
{% if has_sufficient_stock %}
<!-- 使用预计算的结果 -->
{% endif %}
{% endfor %}
2. 智能数据分组
<!-- 按需分组数据 -->
{% assign products_by_vendor = collection.products | group_by: 'vendor' %}
{% assign products_by_type = collection.products | group_by: 'type' %}
{% assign price_ranges = collection.products | group_by: 'price' %}
<!-- 使用分组数据进行高效渲染 -->
{% for vendor_group in products_by_vendor %}
<div class="vendor-section">
<h3>{{ vendor_group.name }}</h3>
<div class="vendor-products">
{% for product in vendor_group.items limit: 8 %}
{% render 'product-card-minimal', product: product %}
{% endfor %}
</div>
</div>
{% endfor %}
3. 条件数据加载
<!-- 根据页面上下文加载数据 -->
{% case template %}
{% when 'index' %}
{% assign featured_products = collections.featured.products | limit: 8 %}
{% assign new_products = collections.new.products | limit: 4 %}
<!-- 首页只加载必要数据 -->
{% when 'collection' %}
{% assign all_products = collection.products %}
{% assign filter_options = collection.products | map: 'vendor' | uniq %}
<!-- 集合页加载完整数据 -->
{% when 'product' %}
{% assign related_products = collections[product.type] | default: collections.all | limit: 4 %}
<!-- 产品页只加载相关产品 -->
{% endcase %}
循环和迭代优化
1. 限制循环次数
<!-- 设置合理的限制 -->
{% assign max_products = 20 %}
{% assign max_reviews = 5 %}
{% assign max_images = 10 %}
{% for product in collection.products limit: max_products %}
<div class="product-item">
<h3>{{ product.title }}</h3>
<!-- 限制嵌套循环 -->
{% for image in product.images limit: max_images %}
<img src="{{ image | img_url: '100x100' }}" alt="{{ image.alt }}">
{% endfor %}
</div>
{% endfor %}
2. 早期跳出优化
<!-- 使用 break 和 continue 优化循环 -->
{% assign featured_count = 0 %}
{% assign max_featured = 6 %}
{% for product in collection.products %}
<!-- 跳过不可用商品 -->
{% unless product.available %}
{% continue %}
{% endunless %}
<!-- 达到目标数量后退出 -->
{% if featured_count >= max_featured %}
{% break %}
{% endif %}
{% if product.tags contains 'featured' %}
{% render 'featured-product-card', product: product %}
{% assign featured_count = featured_count | plus: 1 %}
{% endif %}
{% endfor %}
3. 批处理优化
<!-- 批量处理数据 -->
{% assign batch_size = 10 %}
{% assign total_batches = collection.products.size | divided_by: batch_size | ceil %}
{% for i in (0..total_batches) %}
{% assign start_index = i | times: batch_size %}
{% assign end_index = start_index | plus: batch_size | minus: 1 %}
<div class="product-batch" data-batch="{{ i }}">
{% for product in collection.products limit: batch_size offset: start_index %}
{% render 'product-card', product: product %}
{% endfor %}
</div>
{% endfor %}
过滤器优化
1. 过滤器链优化
<!-- 优化过滤器顺序 - 先减少数据量 -->
{% assign optimized_products = collection.products
| where: 'available', true <!-- 首先过滤可用性 -->
| where: 'price', '>', 0 <!-- 过滤有效价格 -->
| where: 'tags', 'featured' <!-- 然后过滤标签 -->
| sort: 'created_at' <!-- 最后排序 -->
| reverse <!-- 反向排序 -->
| limit: 12 %} <!-- 最终限制数量 -->
<!-- 避免反复过滤同一数据集 -->
{% assign base_products = collection.products | where: 'available', true %}
{% assign featured_products = base_products | where: 'tags', 'featured' %}
{% assign sale_products = base_products | where: 'compare_at_price_max', '>', 0 %}
2. 条件过滤
<!-- 根据条件应用过滤 -->
{% assign filtered_products = collection.products %}
{% if settings.show_only_available %}
{% assign filtered_products = filtered_products | where: 'available', true %}
{% endif %}
{% if settings.min_price > 0 %}
{% assign filtered_products = filtered_products | where: 'price', '>=', settings.min_price %}
{% endif %}
{% if settings.featured_only %}
{% assign filtered_products = filtered_products | where: 'tags', 'featured' %}
{% endif %}
<!-- 最后应用排序和限制 -->
{% case settings.sort_order %}
{% when 'price_asc' %}
{% assign filtered_products = filtered_products | sort: 'price' %}
{% when 'price_desc' %}
{% assign filtered_products = filtered_products | sort: 'price' | reverse %}
{% when 'created_desc' %}
{% assign filtered_products = filtered_products | sort: 'created_at' | reverse %}
{% endcase %}
{% assign display_products = filtered_products | limit: settings.products_per_page %}
3. 缓存过滤结果
<!-- 缓存复杂过滤操作的结果 -->
{% comment %} 概念性缓存 - 实际实现需要后端支持 {% endcomment %}
{% assign cache_key = collection.handle | append: '_filtered_' | append: settings.filter_settings %}
{% unless cache[cache_key] %}
{% assign expensive_filtered_data = collection.products
| where: 'available', true
| map: 'vendor'
| uniq
| sort %}
{% comment %} 存储到缓存 {% endcomment %}
{% endunless %}
{% assign vendor_list = cache[cache_key] | default: expensive_filtered_data %}
对象访问优化
1. 缓存对象引用
<!-- 缓存深层对象访问 -->
{% assign selected_variant = product.selected_or_first_available_variant %}
{% assign variant_image = selected_variant.featured_image %}
{% assign variant_price = selected_variant.price %}
{% assign variant_available = selected_variant.available %}
<!-- 使用缓存的引用 -->
<div class="product-variant">
{% if variant_image %}
<img src="{{ variant_image | img_url: '300x300' }}" alt="{{ product.title }}">
{% endif %}
<div class="variant-info">
<p class="price">{{ variant_price | money }}</p>
<p class="availability">
{% if variant_available %}有库存{% else %}缺货{% endif %}
</p>
</div>
</div>
2. 避免重复对象查找
<!-- 不好的做法 -->
{% for item in cart.items %}
{% if collections[item.product.type] %}
{% for related in collections[item.product.type].products limit: 3 %}
<!-- 每次都查找集合 -->
{% endfor %}
{% endif %}
{% endfor %}
<!-- 好的做法 -->
{% assign product_collections = '' %}
{% for item in cart.items %}
{% assign type_collection = collections[item.product.type] %}
{% if type_collection %}
{% assign collection_key = item.product.type %}
{% unless product_collections contains collection_key %}
{% assign product_collections = product_collections | append: collection_key | append: ',' %}
<!-- 处理集合数据 -->
{% for related in type_collection.products limit: 3 %}
{% render 'related-product', product: related %}
{% endfor %}
{% endunless %}
{% endif %}
{% endfor %}
图片和媒体优化
1. 智能图片尺寸
<!-- 响应式图片尺寸 -->
{% assign base_size = 400 %}
{% if request.user_agent contains 'Mobile' %}
{% assign base_size = 300 %}
{% endif %}
{% assign image_sizes = base_size | append: 'x' | append: base_size %}
{% assign srcset_sizes = '' %}
<!-- 生成 srcset -->
{% for multiplier in (1..3) %}
{% assign size = base_size | times: multiplier %}
{% assign srcset_size = size | append: 'x' | append: size %}
{% assign srcset_entry = product.featured_image | img_url: srcset_size | append: ' ' | append: multiplier | append: 'x' %}
{% if srcset_sizes == '' %}
{% assign srcset_sizes = srcset_entry %}
{% else %}
{% assign srcset_sizes = srcset_sizes | append: ', ' | append: srcset_entry %}
{% endif %}
{% endfor %}
<img src="{{ product.featured_image | img_url: image_sizes }}"
srcset="{{ srcset_sizes }}"
sizes="(max-width: 768px) 300px, 400px"
alt="{{ product.title }}"
loading="lazy">
2. 延迟加载实现
<!-- 使用 Intersection Observer 的延迟加载 -->
<img class="lazy-load"
data-src="{{ product.featured_image | img_url: '400x400' }}"
data-srcset="{{ product.featured_image | img_url: '400x400' }} 1x, {{ product.featured_image | img_url: '800x800' }} 2x"
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 400'%3E%3Crect width='400' height='400' fill='%23f0f0f0'/%3E%3C/svg%3E"
alt="{{ product.title }}"
loading="lazy">
<script>
document.addEventListener('DOMContentLoaded', function() {
const lazyImages = document.querySelectorAll('.lazy-load');
if ('IntersectionObserver' in window) {
const imageObserver = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset;
}
img.classList.remove('lazy-load');
imageObserver.unobserve(img);
}
});
});
lazyImages.forEach(function(img) {
imageObserver.observe(img);
});
} else {
// 降级处理
lazyImages.forEach(function(img) {
img.src = img.dataset.src;
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset;
}
});
}
});
</script>
3. 预加载关键资源
<!-- 预加载关键图片 -->
{% if product.featured_image %}
<link rel="preload"
as="image"
href="{{ product.featured_image | img_url: '600x600' }}"
imagesrcset="{{ product.featured_image | img_url: '300x300' }} 300w,
{{ product.featured_image | img_url: '600x600' }} 600w,
{{ product.featured_image | img_url: '1200x1200' }} 1200w">
{% endif %}
<!-- 预加载关键字体 -->
<link rel="preload" href="{{ 'font-primary.woff2' | asset_url }}" as="font" type="font/woff2" crossorigin>
<!-- 预连接到外部资源 -->
<link rel="preconnect" href="https://cdn.shopify.com">
<link rel="dns-prefetch" href="https://monorail-edge.shopifysvc.com">
代码分割和模块化
1. 条件加载组件
<!-- 只在需要时加载复杂组件 -->
{% if template contains 'product' %}
{% render 'product-recommendations', product: product %}
{% render 'product-reviews', product: product %}
{% endif %}
{% if template contains 'cart' %}
{% render 'cart-upsells' %}
{% render 'shipping-calculator' %}
{% endif %}
{% if customer %}
{% render 'customer-dashboard' %}
{% else %}
{% render 'guest-features' %}
{% endif %}
2. 异步内容加载
<!-- 标记异步加载区域 -->
<div id="product-reviews"
data-product-id="{{ product.id }}"
data-load-async="true">
<div class="loading-skeleton">
<div class="skeleton-text"></div>
<div class="skeleton-text"></div>
<div class="skeleton-text"></div>
</div>
</div>
<div id="related-products"
data-collection="{{ product.type | handle }}"
data-load-async="true">
<div class="loading-skeleton">
{% for i in (1..4) %}
<div class="skeleton-product-card"></div>
{% endfor %}
</div>
</div>
<script>
// 异步加载内容
document.addEventListener('DOMContentLoaded', function() {
const asyncElements = document.querySelectorAll('[data-load-async]');
asyncElements.forEach(function(element) {
// 使用 Intersection Observer 在元素进入视口时加载
const observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
loadAsyncContent(entry.target);
observer.unobserve(entry.target);
}
});
});
observer.observe(element);
});
});
</script>
3. 渐进式渲染
<!-- 分批渲染大量内容 -->
{% assign total_products = collection.products.size %}
{% assign batch_size = 12 %}
{% assign initial_batch = batch_size %}
<!-- 首批内容立即渲染 -->
<div class="products-initial">
{% for product in collection.products limit: initial_batch %}
{% render 'product-card', product: product %}
{% endfor %}
</div>
<!-- 剩余内容延迟渲染 -->
{% if total_products > initial_batch %}
<div class="products-lazy" style="display: none;">
{% for product in collection.products offset: initial_batch %}
{% render 'product-card', product: product %}
{% endfor %}
</div>
<button id="load-more" class="btn btn-secondary">
加载更多 ({{ total_products | minus: initial_batch }} 个商品)
</button>
<script>
document.getElementById('load-more').addEventListener('click', function() {
const lazyProducts = document.querySelector('.products-lazy');
lazyProducts.style.display = 'block';
this.style.display = 'none';
});
</script>
{% endif %}
缓存策略
1. 模板级缓存
<!-- 使用 metafields 进行简单缓存 -->
{% assign cache_key = 'expensive_calculation_' | append: collection.id %}
{% assign cache_ttl = 3600 %} <!-- 1小时 -->
{% assign current_time = 'now' | date: '%s' | plus: 0 %}
{% assign cached_result = shop.metafields.cache[cache_key] %}
{% assign cache_time = shop.metafields.cache[cache_key | append: '_time'] | plus: 0 %}
{% if cached_result == blank or current_time | minus: cache_time > cache_ttl %}
<!-- 重新计算 -->
{% assign expensive_result = collection.products
| where: 'available', true
| group_by: 'vendor'
| map: 'name'
| join: ',' %}
{% comment %}
在实际应用中,这里需要后端API来设置metafields
{% endcomment %}
{% assign cached_result = expensive_result %}
{% endif %}
<!-- 使用缓存的结果 -->
{{ cached_result }}
2. 浏览器缓存优化
<!-- 设置适当的缓存头 -->
<script>
// 利用浏览器缓存存储计算结果
const cacheKey = 'collection_{{ collection.id }}_filters';
const cacheTime = 'collection_{{ collection.id }}_time';
const maxAge = 1800000; // 30分钟
let cachedData = localStorage.getItem(cacheKey);
let cachedTime = localStorage.getItem(cacheTime);
if (!cachedData || !cachedTime || Date.now() - parseInt(cachedTime) > maxAge) {
// 重新计算并缓存
const freshData = calculateExpensiveData();
localStorage.setItem(cacheKey, JSON.stringify(freshData));
localStorage.setItem(cacheTime, Date.now().toString());
cachedData = freshData;
} else {
cachedData = JSON.parse(cachedData);
}
</script>
性能监控和分析
1. 实时性能监控
<!-- 性能监控脚本 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const perfData = {
// 页面加载性能
loadTime: performance.timing.loadEventEnd - performance.timing.navigationStart,
domReady: performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart,
firstPaint: performance.getEntriesByType('paint')[0]?.startTime || 0,
// Liquid 特定指标
liquidObjects: {
products: {{ collection.products.size | default: 0 }},
variants: {{ product.variants.size | default: 0 }},
images: {{ product.images.size | default: 0 }}
},
// 页面类型
template: '{{ template }}',
pageType: '{{ request.page_type }}'
};
// 发送性能数据到分析服务
if (window.gtag) {
gtag('event', 'page_performance', {
custom_parameter_1: perfData.loadTime,
custom_parameter_2: perfData.liquidObjects.products
});
}
// 开发环境下显示性能信息
{% if settings.debug_mode %}
console.log('页面性能数据:', perfData);
{% endif %}
});
</script>
2. Core Web Vitals 优化
<!-- Core Web Vitals 优化 -->
<script>
// 监控 LCP (Largest Contentful Paint)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.startTime);
}).observe({entryTypes: ['largest-contentful-paint']});
// 监控 FID (First Input Delay)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach((entry) => {
console.log('FID:', entry.processingStart - entry.startTime);
});
}).observe({entryTypes: ['first-input']});
// 监控 CLS (Cumulative Layout Shift)
let clsValue = 0;
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
console.log('CLS:', clsValue);
}).observe({entryTypes: ['layout-shift']});
</script>
移动端优化
1. 移动端特定优化
<!-- 检测移动设备 -->
{% assign is_mobile = request.user_agent contains 'Mobile' %}
{% assign is_tablet = request.user_agent contains 'Tablet' %}
<!-- 移动端优化的图片尺寸 -->
{% if is_mobile %}
{% assign image_size = '300x300' %}
{% assign products_per_row = 2 %}
{% assign max_products = 8 %}
{% elsif is_tablet %}
{% assign image_size = '400x400' %}
{% assign products_per_row = 3 %}
{% assign max_products = 12 %}
{% else %}
{% assign image_size = '500x500' %}
{% assign products_per_row = 4 %}
{% assign max_products = 16 %}
{% endif %}
<!-- 移动端优化的布局 -->
<div class="products-grid mobile-optimized"
data-columns="{{ products_per_row }}">
{% for product in collection.products limit: max_products %}
<div class="product-card">
<img src="{{ product.featured_image | img_url: image_size }}"
alt="{{ product.title }}"
loading="lazy">
<h3>{{ product.title | truncate: 30 }}</h3>
<p>{{ product.price | money }}</p>
</div>
{% endfor %}
</div>
2. 触摸优化
<!-- 为移动设备优化的交互元素 -->
{% if is_mobile %}
<style>
.product-card {
/* 增大触摸目标 */
min-height: 44px;
padding: 12px;
}
.btn {
/* 移动端按钮优化 */
min-height: 44px;
font-size: 16px; /* 防止iOS缩放 */
}
.touch-friendly {
/* 为触摸优化的间距 */
margin: 8px 0;
padding: 12px 16px;
}
</style>
{% endif %}
总结和最佳实践
性能优化清单
-
数据预处理
- 缓存复杂计算结果
- 减少重复过滤操作
- 合理使用 limit 限制数据量
-
循环优化
- 使用 break 和 continue
- 避免深层嵌套循环
- 批处理大量数据
-
对象访问
- 缓存深层对象引用
- 避免重复查找
- 使用条件加载
-
图片优化
- 响应式图片尺寸
- 延迟加载
- 预加载关键资源
-
代码组织
- 模块化组件
- 条件渲染
- 异步加载
下一步学习
掌握性能优化后,建议继续学习:
性能优化是一个持续的过程,需要在开发中不断监控、测试和改进!
最后更新时间: