wordPress源码-add-to-cart-variation.js相关功能

学习产品属性选择以及购物车前端业务流程,即:add-to-cart-variation.js,记录一些知识点,笔记备忘。

1:关于Woo原型链模式

发现调用更新产品变体属性功能onUpdateAttributes

var VariationForm = function( $form ) {
  var self = this;
  self.$form  = $form;
  ...
  $form.on( 'update_variation_values.wc-variation-form', { variationForm: self }, self.onUpdateAttributes );

通过以下方式调用:

VariationForm.prototype.onUpdateAttributes = function( event ) {
  var form  = event.data.variationForm,

而不是直接在构造函数里创建方法,如下:

var VariationForm = function( $form ) {
    // 将方法直接定义在构造函数内部
    this.onUpdateAttributes = function( event ) {
        // 方法实现
    };
};

进一步了解,得知第一种调用方式是原型链方式,结构如下:

var VariationForm = function( $form ) {
    // 初始化代码
};

// 将方法添加到原型链
VariationForm.prototype.onUpdateAttributes = function( event ) {
    // 方法实现
};

这种方式的最大用处是节省内存

例如在电商页面中,可能同时存在多个可变产品表单(如产品列表页)。如果每个表单实例都复制一份方法,会显著增加内存占用。例如:

// 创建1000个表单实例
for (let i = 0; i < 1000; i++) {
    new VariationForm($(`.form-${i}`));
}

采用原型链方式,所有实例共享同一个 onUpdateAttributes 方法。而基于构造函数内部方法,则需要创建 1000 个独立的方法副本。

2:js事件绑定在 DOM 元素上,而非对象

Woo 产品属性变体选择默认是下拉菜单,我把它改为单选按钮方式。add-to-cart-variation.js中的部分功能受到影响,尤其是那些直接绑定到下拉菜单事件处理函数。

常见的作法是将原始下拉菜单隐藏但保留在 DOM 中,然后用用户操作单选按钮时,事件转发给隐藏的下拉按钮,再由下拉按钮触发原生add-to-cart-variation.js相关功能。代码如下:

$(".radio-variation-wrapper input[type=\"radio\"]").on("change", function() {
    // 获取当前被点击的单选按钮
    var $radio = $(this);
    // 从父容器获取属性名称(例如:attribute_pa_color)
    var attributeName = $radio.closest('.radio-variation-wrapper').data('attribute_name');
    // 找到对应的隐藏下拉选择器
    var $select = $('select[name="' + attributeName + '"]');
    // 更新下拉选择器的值并触发change事件
    $select.val($radio.val()).trigger('change');

    // 手动触发WooCommerce的购物车更新事件
    $(document.body).trigger('update_checkout', { update_shipping: true });
  });

而在Woo种,已经使用原型模式 创建 VariationForm 对象,并在初始化时绑定事件监听器:

// WooCommerce 核心代码(位于 add-to-cart-variation.js)
function VariationForm( $form ) {
    this.$form = $form;
    this.init();
}

VariationForm.prototype.init = function() {
    // 监听下拉菜单的 change 事件
    this.$form.on( 'change', '.variations select', this.onChange );
    
    // 其他初始化逻辑...
};

VariationForm.prototype.onChange = function( event ) {
    // 处理变体选择的核心逻辑
    this._reset_variations();
    this._maybe_update_variation();
    this._display_price();
    // 其他逻辑...
};

因此,$select.val($radio.val()).trigger(‘change’); 该操作它模拟了用户直接操作原生下拉菜单的行为,触发了 WooCommerce 核心 JavaScript 已经监听的 DOM 事件。

注意:我上面的代码可以与原生监听交互。即执行

$select.val($radio.val()).trigger(‘change’); 时,这里的 $select 是原生下拉菜单的 jQuery 对象,与 WooCommerce 监听的是同一个 DOM 元素。所以,这个操作会触发该元素上所有绑定的 change 事件监听器,包括 WooCommerce 通过 VariationForm 绑定的监听器。

所以关键知识点:

JavaScript 中的事件监听是基于 DOM 元素 的,而非创建监听器的对象。即使 VariationForm 创建了事件监听器,这些监听器实际上是绑定到 DOM 元素(下拉菜单)上的。

所以,我自定义的代码可以通过触发同一个 DOM 元素的 change 事件,间接调用了 WooCommerce 的原生功能。

3:关于form.$form.trigger功能

form.$form.trigger( 'woocommerce_update_variation_values' );

这个功能类似于wordpress动作钩子,用户自定义的事件可以在该代码执行后触发。比如我现在把Woo 产品属性变体选择默认是下拉菜单,我把它改为单选按钮方式。其中add-to-cart-variation.js中功能

VariationForm.prototype.onUpdateAttributes

不起作用,得重写该功能。该功能也预留了自定义事件接口,如下:

VariationForm.prototype.onUpdateAttributes = function( event ) {
    var form          = event.data.variationForm,
    attributes        = form.getChosenAttributes(),
...
    form.$form.trigger( 'woocommerce_update_variation_values' );//预留接口

我新建个js文件add-to-cart-custom.js ,监听woocommerce_update_variation_values 事件,如下:

jQuery( document ).on( 'woocommerce_update_variation_values', function() {
    console.log( '变体值已更新1' );
});

只要trigger触发该事件,则会被document 上绑定了该事件的监听器所监听到,触发对应的回调函数。

为什么会被监听到呢?原理如下:

document
  └── body
      └── form (form.$form)
          └── select (变体选择器)

上面是页面结构。当用户在 <select> 或 <input> 中选择变体选项时,VariationForm 实例捕获到选择变化,执行onUpdateAttributes功能,最后的form.$form.trigger(‘woocommerce_update_variation_values’) 事件被触发,然后该事件冒泡到 document,于是所有通过 jQuery(document).on() 注册的监听器被触发,从而执行自定义逻辑。

重点:jQuery(document).on()是全局事件监听,用于捕获在整个文档中触发的特定事件。无论事件是在哪个元素上触发的,只要它冒泡到 document,所有绑定在 document 上的该事件监听器都会被触发。这就是为什么不同 JS 文件中定义的监听器都能响应同一事件的原因。