2.1 Sciter-JS 语法概述

2.1.1 语法特性

Sciter-JS 基于标准 JavaScript ES6+ 语法,同时提供了一些扩展特性:

graph TD
    A[Sciter-JS 语法] --> B[标准 JavaScript]
    A --> C[Sciter 扩展]
    B --> D[ES6+ 特性]
    B --> E[异步编程]
    C --> F[DOM 扩展]
    C --> G[系统 API]
    C --> H[原生绑定]

2.1.2 基本数据类型

// 基本数据类型
let number = 42;
let string = "Hello Sciter";
let boolean = true;
let array = [1, 2, 3];
let object = { name: "Sciter", version: "5.0" };

// Sciter 特有类型
let element = document.querySelector("#myElement");
let graphics = new Graphics(element);
let image = Image.load("path/to/image.png");

2.1.3 变量声明与作用域

// 变量声明
let localVar = "局部变量";
const CONSTANT = "常量";
var globalVar = "全局变量"; // 不推荐

// 块级作用域
{
    let blockScoped = "块级作用域";
    console.log(blockScoped); // 可访问
}
// console.log(blockScoped); // 错误:无法访问

// 函数作用域
function myFunction() {
    let functionScoped = "函数作用域";
    return functionScoped;
}

2.2 函数与对象

2.2.1 函数定义

// 函数声明
function greet(name) {
    return `Hello, ${name}!`;
}

// 函数表达式
const greetExpression = function(name) {
    return `Hello, ${name}!`;
};

// 箭头函数
const greetArrow = (name) => `Hello, ${name}!`;

// 异步函数
async function fetchData(url) {
    try {
        const response = await fetch(url);
        return await response.json();
    } catch (error) {
        console.error("获取数据失败:", error);
        throw error;
    }
}

2.2.2 对象操作

// 对象字面量
const user = {
    name: "张三",
    age: 25,
    email: "zhangsan@example.com",
    
    // 方法定义
    greet() {
        return `你好,我是 ${this.name}`;
    },
    
    // 计算属性
    get fullInfo() {
        return `${this.name} (${this.age}岁)`;
    },
    
    set updateAge(newAge) {
        if (newAge > 0) {
            this.age = newAge;
        }
    }
};

// 对象解构
const { name, age } = user;
console.log(name, age);

// 对象扩展
const extendedUser = {
    ...user,
    city: "北京",
    country: "中国"
};

2.2.3 类与继承

// 基类定义
class Component {
    constructor(element) {
        this.element = element;
        this.initialized = false;
    }
    
    init() {
        if (!this.initialized) {
            this.setupEvents();
            this.initialized = true;
        }
    }
    
    setupEvents() {
        // 子类实现
    }
    
    destroy() {
        this.element = null;
        this.initialized = false;
    }
}

// 继承
class Button extends Component {
    constructor(element, options = {}) {
        super(element);
        this.options = {
            clickable: true,
            ...options
        };
    }
    
    setupEvents() {
        if (this.options.clickable) {
            this.element.on("click", this.handleClick.bind(this));
        }
    }
    
    handleClick(event) {
        console.log("按钮被点击", event);
        this.element.dispatchEvent(new CustomEvent("button-click", {
            detail: { button: this }
        }));
    }
}

2.3 DOM 基础操作

2.3.1 元素选择

// 基本选择器
const elementById = document.getElementById("myId");
const elementsByClass = document.getElementsByClassName("myClass");
const elementsByTag = document.getElementsByTagName("div");

// CSS 选择器
const singleElement = document.querySelector(".container > .item:first-child");
const multipleElements = document.querySelectorAll(".item[data-active='true']");

// Sciter 扩展选择器
const elementByPath = document.selectByPath("body > div:nth-child(2) > span");
const elementsByXPath = document.selectAllByXPath("//div[@class='item']");

// 相对选择
const parent = element.parentElement;
const children = element.children;
const siblings = element.parentElement.children;
const nextSibling = element.nextElementSibling;
const prevSibling = element.previousElementSibling;

2.3.2 元素属性操作

// 属性读写
const element = document.querySelector("#myElement");

// 标准属性
element.id = "newId";
element.className = "new-class";
element.textContent = "新文本内容";
element.innerHTML = "<span>HTML 内容</span>";

// 自定义属性
element.setAttribute("data-value", "123");
const dataValue = element.getAttribute("data-value");
element.removeAttribute("data-old");

// 属性检查
if (element.hasAttribute("data-value")) {
    console.log("元素具有 data-value 属性");
}

// 数据集操作
element.dataset.userId = "user123";
element.dataset.status = "active";
console.log(element.dataset.userId); // "user123"

// 样式操作
element.style.color = "red";
element.style.backgroundColor = "#f0f0f0";
element.style.setProperty("--custom-var", "value");

// CSS 类操作
element.classList.add("active", "highlighted");
element.classList.remove("inactive");
element.classList.toggle("visible");
if (element.classList.contains("active")) {
    console.log("元素包含 active 类");
}

2.3.3 元素创建与插入

// 创建元素
const newDiv = document.createElement("div");
newDiv.className = "new-item";
newDiv.textContent = "新创建的元素";

// 创建文本节点
const textNode = document.createTextNode("纯文本节点");

// 创建文档片段
const fragment = document.createDocumentFragment();
for (let i = 0; i < 5; i++) {
    const item = document.createElement("li");
    item.textContent = `项目 ${i + 1}`;
    fragment.appendChild(item);
}

// 插入元素
const container = document.querySelector(".container");

// 末尾插入
container.appendChild(newDiv);
container.appendChild(fragment);

// 指定位置插入
const referenceElement = container.firstElementChild;
container.insertBefore(newDiv, referenceElement);

// 相邻插入
referenceElement.insertAdjacentElement("beforebegin", newDiv);
referenceElement.insertAdjacentElement("afterbegin", newDiv);
referenceElement.insertAdjacentElement("beforeend", newDiv);
referenceElement.insertAdjacentElement("afterend", newDiv);

// HTML 字符串插入
container.insertAdjacentHTML("beforeend", "<p>通过 HTML 字符串插入</p>");

2.3.4 元素删除与替换

// 删除元素
const elementToRemove = document.querySelector(".to-remove");
if (elementToRemove) {
    elementToRemove.remove(); // 现代方法
    // 或者
    elementToRemove.parentElement.removeChild(elementToRemove); // 传统方法
}

// 清空容器
const container = document.querySelector(".container");
container.innerHTML = ""; // 简单但可能有内存泄漏
// 或者
while (container.firstChild) {
    container.removeChild(container.firstChild);
}

// 替换元素
const oldElement = document.querySelector(".old-element");
const newElement = document.createElement("div");
newElement.className = "new-element";
newElement.textContent = "替换后的元素";
oldElement.parentElement.replaceChild(newElement, oldElement);

2.4 Sciter 特有的 DOM 扩展

2.4.1 元素状态管理

// 元素状态
const button = document.querySelector("button");

// 设置状态
button.state.disabled = true;
button.state.checked = false;
button.state.expanded = true;

// 读取状态
if (button.state.disabled) {
    console.log("按钮已禁用");
}

// 状态变化监听
button.on("statechange", function(event) {
    console.log("状态变化:", event.detail);
});

// 自定义状态
button.state.custom = "myValue";
button.setAttribute(":custom", "myValue"); // 等效写法

2.4.2 元素几何信息

const element = document.querySelector(".my-element");

// 获取尺寸和位置
const rect = element.getBoundingClientRect();
console.log("位置:", rect.left, rect.top);
console.log("尺寸:", rect.width, rect.height);

// Sciter 扩展的几何信息
const box = element.box;
console.log("内容区域:", box.content);
console.log("内边距区域:", box.padding);
console.log("边框区域:", box.border);
console.log("外边距区域:", box.margin);

// 滚动信息
console.log("滚动位置:", element.scrollLeft, element.scrollTop);
console.log("滚动尺寸:", element.scrollWidth, element.scrollHeight);
console.log("客户端尺寸:", element.clientWidth, element.clientHeight);

2.4.3 元素动画

// CSS 过渡动画
const element = document.querySelector(".animated-element");

// 设置过渡
element.style.transition = "all 0.3s ease";
element.style.transform = "translateX(100px)";

// 监听动画事件
element.on("transitionend", function(event) {
    console.log("过渡动画结束", event.propertyName);
});

// Sciter 动画 API
element.animate({
    transform: "scale(1.2) rotate(45deg)",
    opacity: 0.8
}, {
    duration: 500,
    easing: "ease-in-out",
    fill: "forwards"
}).then(() => {
    console.log("动画完成");
});

// 关键帧动画
element.animate([
    { transform: "translateX(0px)", opacity: 1 },
    { transform: "translateX(50px)", opacity: 0.5 },
    { transform: "translateX(100px)", opacity: 1 }
], {
    duration: 1000,
    iterations: 2,
    direction: "alternate"
});

2.5 实践练习

2.5.1 动态列表管理器

class ListManager {
    constructor(containerSelector) {
        this.container = document.querySelector(containerSelector);
        this.items = [];
        this.init();
    }
    
    init() {
        this.createControls();
        this.setupEvents();
    }
    
    createControls() {
        const controls = document.createElement("div");
        controls.className = "list-controls";
        controls.innerHTML = `
            <input type="text" id="itemInput" placeholder="输入项目内容">
            <button id="addBtn">添加</button>
            <button id="clearBtn">清空</button>
        `;
        
        const list = document.createElement("ul");
        list.className = "dynamic-list";
        
        this.container.appendChild(controls);
        this.container.appendChild(list);
        
        this.input = controls.querySelector("#itemInput");
        this.addBtn = controls.querySelector("#addBtn");
        this.clearBtn = controls.querySelector("#clearBtn");
        this.list = list;
    }
    
    setupEvents() {
        this.addBtn.on("click", () => this.addItem());
        this.clearBtn.on("click", () => this.clearItems());
        this.input.on("keypress", (e) => {
            if (e.key === "Enter") {
                this.addItem();
            }
        });
    }
    
    addItem() {
        const text = this.input.value.trim();
        if (!text) return;
        
        const item = {
            id: Date.now(),
            text: text,
            created: new Date()
        };
        
        this.items.push(item);
        this.renderItem(item);
        this.input.value = "";
        this.input.focus();
    }
    
    renderItem(item) {
        const li = document.createElement("li");
        li.className = "list-item";
        li.dataset.id = item.id;
        li.innerHTML = `
            <span class="item-text">${item.text}</span>
            <span class="item-time">${item.created.toLocaleTimeString()}</span>
            <button class="remove-btn">删除</button>
        `;
        
        // 添加删除事件
        const removeBtn = li.querySelector(".remove-btn");
        removeBtn.on("click", () => this.removeItem(item.id));
        
        // 添加动画效果
        li.style.opacity = "0";
        li.style.transform = "translateY(-20px)";
        this.list.appendChild(li);
        
        // 触发动画
        requestAnimationFrame(() => {
            li.style.transition = "all 0.3s ease";
            li.style.opacity = "1";
            li.style.transform = "translateY(0)";
        });
    }
    
    removeItem(id) {
        const itemElement = this.list.querySelector(`[data-id="${id}"]`);
        if (itemElement) {
            itemElement.style.transition = "all 0.3s ease";
            itemElement.style.opacity = "0";
            itemElement.style.transform = "translateX(100%)";
            
            setTimeout(() => {
                itemElement.remove();
            }, 300);
        }
        
        this.items = this.items.filter(item => item.id !== id);
    }
    
    clearItems() {
        this.items = [];
        this.list.innerHTML = "";
    }
    
    getItems() {
        return [...this.items];
    }
}

// 使用示例
document.ready = function() {
    const listManager = new ListManager(".app-container");
    
    // 添加一些初始数据
    setTimeout(() => {
        listManager.input.value = "示例项目 1";
        listManager.addItem();
        listManager.input.value = "示例项目 2";
        listManager.addItem();
    }, 500);
};

2.5.2 表单验证器

class FormValidator {
    constructor(formSelector) {
        this.form = document.querySelector(formSelector);
        this.rules = new Map();
        this.errors = new Map();
        this.init();
    }
    
    init() {
        this.setupEvents();
    }
    
    setupEvents() {
        this.form.on("submit", (e) => {
            e.preventDefault();
            this.validate();
        });
        
        // 实时验证
        this.form.on("input", (e) => {
            if (e.target.matches("input, textarea, select")) {
                this.validateField(e.target);
            }
        });
    }
    
    addRule(fieldName, validator, message) {
        if (!this.rules.has(fieldName)) {
            this.rules.set(fieldName, []);
        }
        this.rules.get(fieldName).push({ validator, message });
        return this;
    }
    
    validateField(field) {
        const fieldName = field.name;
        const value = field.value;
        const rules = this.rules.get(fieldName) || [];
        
        this.errors.delete(fieldName);
        
        for (const rule of rules) {
            if (!rule.validator(value, field)) {
                this.errors.set(fieldName, rule.message);
                break;
            }
        }
        
        this.updateFieldUI(field);
        return !this.errors.has(fieldName);
    }
    
    validate() {
        const fields = this.form.querySelectorAll("input, textarea, select");
        let isValid = true;
        
        fields.forEach(field => {
            if (!this.validateField(field)) {
                isValid = false;
            }
        });
        
        if (isValid) {
            this.onSuccess();
        } else {
            this.onError();
        }
        
        return isValid;
    }
    
    updateFieldUI(field) {
        const fieldName = field.name;
        const errorElement = this.form.querySelector(`[data-error-for="${fieldName}"]`);
        
        if (this.errors.has(fieldName)) {
            field.classList.add("error");
            field.classList.remove("valid");
            
            if (errorElement) {
                errorElement.textContent = this.errors.get(fieldName);
                errorElement.style.display = "block";
            }
        } else {
            field.classList.remove("error");
            field.classList.add("valid");
            
            if (errorElement) {
                errorElement.style.display = "none";
            }
        }
    }
    
    onSuccess() {
        console.log("表单验证成功");
        // 提交表单数据
        const formData = new FormData(this.form);
        console.log("表单数据:", Object.fromEntries(formData));
    }
    
    onError() {
        console.log("表单验证失败", this.errors);
    }
    
    // 预定义验证器
    static validators = {
        required: (value) => value.trim() !== "",
        email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
        minLength: (min) => (value) => value.length >= min,
        maxLength: (max) => (value) => value.length <= max,
        pattern: (regex) => (value) => regex.test(value),
        number: (value) => !isNaN(value) && value !== "",
        range: (min, max) => (value) => {
            const num = parseFloat(value);
            return !isNaN(num) && num >= min && num <= max;
        }
    };
}

// 使用示例
document.ready = function() {
    const validator = new FormValidator("#myForm");
    
    validator
        .addRule("username", FormValidator.validators.required, "用户名不能为空")
        .addRule("username", FormValidator.validators.minLength(3), "用户名至少3个字符")
        .addRule("email", FormValidator.validators.required, "邮箱不能为空")
        .addRule("email", FormValidator.validators.email, "邮箱格式不正确")
        .addRule("age", FormValidator.validators.number, "年龄必须是数字")
        .addRule("age", FormValidator.validators.range(1, 120), "年龄必须在1-120之间");
};

2.6 本章小结

2.6.1 核心概念

  • 语法基础:Sciter-JS 基于标准 JavaScript,提供额外的 DOM 和系统 API
  • DOM 操作:元素选择、属性操作、内容修改、结构变更
  • 事件处理:标准事件模型加上 Sciter 特有的扩展
  • 状态管理:元素状态的读取和设置

2.6.2 技术要点

  1. 选择器优化:合理使用不同类型的选择器提高性能
  2. 内存管理:及时清理事件监听器和引用
  3. 动画性能:使用 CSS 过渡和 transform 属性
  4. 代码组织:使用类和模块化方式组织代码

2.6.3 最佳实践

  • 使用现代 JavaScript 语法(ES6+)
  • 避免直接操作 innerHTML,优先使用 DOM API
  • 合理使用事件委托减少内存占用
  • 编写可复用的组件类

2.6.4 下一章预告

下一章将深入学习 事件处理与用户交互,包括: - 事件模型和事件流 - 鼠标、键盘、触摸事件 - 自定义事件和事件委托 - 用户交互模式和最佳实践

通过本章的学习,你已经掌握了 Sciter-JS 的基础语法和 DOM 操作技能,为后续的高级功能学习打下了坚实基础。