<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title><![CDATA[Mr 焦]]></title> 
<atom:link href="https://www.mtsws.cn/rss.php" rel="self" type="application/rss+xml" />
<description><![CDATA[90后码农，爱研究瞎折腾，爱文学烂笔头。]]></description>
<link>https://www.mtsws.cn/</link>
<language>zh-cn</language>
<generator>www.emlog.net</generator>
<item>
    <title>Sequelize 在 Node.js 中的详细用法与使用笔记</title>
    <link>https://www.mtsws.cn/post-17.html</link>
    <description><![CDATA[<h2>1. Sequelize 简介</h2>
<p>Sequelize 是一个基于 Promise 的 Node.js ORM (Object-Relational Mapping) 工具，支持 PostgreSQL、MySQL、MariaDB、SQLite 和 Microsoft SQL Server 等多种数据库。</p>
<h2>2. 安装与基本配置</h2>
<h3>安装</h3>
<pre><code class="language-bash">npm install sequelize
# 根据使用的数据库安装对应的驱动
npm install pg pg-hstore # PostgreSQL
npm install mysql2 # MySQL
npm install mariadb # MariaDB
npm install sqlite3 # SQLite
npm install tedious # Microsoft SQL Server</code></pre>
<h3>基本连接配置</h3>
<pre><code class="language-javascript">const { Sequelize } = require('sequelize');

// 方法1: 通过URI连接
const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname');

// 方法2: 通过参数连接
const sequelize = new Sequelize('database', 'username', 'password', {
  host: 'localhost',
  dialect: 'mysql', // 或其他数据库类型
  // 其他选项
  pool: {
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000
  },
  logging: console.log, // 显示日志
  // 对于SQLite
  // storage: 'path/to/database.sqlite'
});</code></pre>
<h2>3. 模型定义</h2>
<h3>基本模型定义</h3>
<pre><code class="language-javascript">const { DataTypes } = require('sequelize');

const User = sequelize.define('User', {
  // 模型属性
  firstName: {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName: {
    type: DataTypes.STRING
    // allowNull 默认为 true
  },
  age: {
    type: DataTypes.INTEGER,
    defaultValue: 18
  },
  bio: {
    type: DataTypes.TEXT
  },
  isAdmin: {
    type: DataTypes.BOOLEAN,
    defaultValue: false
  }
}, {
  // 模型选项
  tableName: 'users', // 指定表名
  timestamps: true, // 默认添加 createdAt 和 updatedAt 字段
  // createdAt: 'created_at', // 自定义字段名
  // updatedAt: 'updated_at',
  // paranoid: true, // 软删除，添加 deletedAt 字段
  // underscored: true, // 将驼峰命名转换为下划线
  // freezeTableName: true // 防止 Sequelize 自动复数化表名
});</code></pre>
<h3>数据类型</h3>
<p>Sequelize 提供了多种数据类型：</p>
<ul>
<li><code>STRING</code> / <code>TEXT</code> - 字符串/文本</li>
<li><code>INTEGER</code> / <code>BIGINT</code> / <code>FLOAT</code> / <code>DOUBLE</code> / <code>DECIMAL</code> - 数字</li>
<li><code>BOOLEAN</code> - 布尔值</li>
<li><code>DATE</code> / <code>DATEONLY</code> / <code>TIME</code> - 日期时间</li>
<li><code>UUID</code> - UUID</li>
<li><code>ENUM</code> - 枚举值</li>
<li><code>JSON</code> / <code>JSONB</code> - JSON 数据</li>
<li><code>BLOB</code> - 二进制数据</li>
</ul>
<h2>4. 模型同步</h2>
<pre><code class="language-javascript">// 同步单个模型
await User.sync({ force: true }); // 强制同步，会删除现有表

// 同步所有模型
await sequelize.sync({ alter: true }); // 安全同步，只修改表结构以匹配模型

// 生产环境通常使用迁移而不是 sync()</code></pre>
<h2>5. CRUD 操作</h2>
<h3>创建记录</h3>
<pre><code class="language-javascript">// 方法1: build + save
const user = User.build({
  firstName: 'John',
  lastName: 'Doe'
});
await user.save();

// 方法2: create (build + save 的快捷方式)
const user = await User.create({
  firstName: 'Jane',
  lastName: 'Doe',
  age: 25
});</code></pre>
<h3>查询记录</h3>
<pre><code class="language-javascript">// 查找所有记录
const users = await User.findAll();

// 带条件查询
const users = await User.findAll({
  where: {
    age: {
      [Op.gt]: 18 // 大于18岁
    }
  },
  order: [['age', 'DESC']],
  limit: 10
});

// 查找单个记录
const user = await User.findOne({
  where: { firstName: 'John' }
});

// 通过主键查找
const user = await User.findByPk(1);

// 计数
const count = await User.count({
  where: {
    age: {
      [Op.gt]: 20
    }
  }
});</code></pre>
<h3>更新记录</h3>
<pre><code class="language-javascript">// 方法1: 先查询再更新
const user = await User.findByPk(1);
if (user) {
  user.lastName = 'Smith';
  await user.save();
}

// 方法2: 直接更新
await User.update(
  { lastName: 'Smith' },
  {
    where: {
      firstName: 'John'
    }
  }
);</code></pre>
<h3>删除记录</h3>
<pre><code class="language-javascript">// 方法1: 先查询再删除
const user = await User.findByPk(1);
if (user) {
  await user.destroy();
}

// 方法2: 直接删除
await User.destroy({
  where: {
    age: {
      [Op.lt]: 18 // 删除所有年龄小于18的记录
    }
  }
});

// 软删除 (需要启用 paranoid: true)
await user.destroy(); // 设置 deletedAt
await user.restore(); // 恢复软删除的记录</code></pre>
<h2>6. 查询操作符</h2>
<p>Sequelize 提供了丰富的查询操作符：</p>
<pre><code class="language-javascript">const { Op } = require('sequelize');

// 基本比较
[Op.eq]: 3,              // = 3
[Op.ne]: 20,             // != 20
[Op.gt]: 6,              // &gt; 6
[Op.gte]: 6,             // &gt;= 6
[Op.lt]: 10,             // &lt; 10
[Op.lte]: 10,            // &lt;= 10

// 范围
[Op.between]: [6, 10],   // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15

// 集合
[Op.in]: [1, 2],         // IN [1, 2]
[Op.notIn]: [1, 2],      // NOT IN [1, 2]

// 模糊匹配
[Op.like]: '%hat',       // LIKE '%hat'
[Op.notLike]: '%hat',    // NOT LIKE '%hat'
[Op.startsWith]: 'hat',  // LIKE 'hat%'
[Op.endsWith]: 'hat',    // LIKE '%hat'
[Op.substring]: 'hat',   // LIKE '%hat%'

// 逻辑
[Op.and]: [{a: 5}, {b: 6}], // AND (a = 5) AND (b = 6)
[Op.or]: [{a: 5}, {a: 6}],  // OR (a = 5 OR a = 6)
[Op.not]: { age: 10 }       // NOT (age = 10)</code></pre>
<h2>7. 关联关系</h2>
<h3>一对一关系</h3>
<pre><code class="language-javascript">// User 有一个 Profile
User.hasOne(Profile);
Profile.belongsTo(User);

// 使用
const user = await User.create({ /* ... */ });
const profile = await Profile.create({ /* ... */ });
await user.setProfile(profile);
const userProfile = await user.getProfile();</code></pre>
<h3>一对多关系</h3>
<pre><code class="language-javascript">// User 有多个 Post
User.hasMany(Post);
Post.belongsTo(User);

// 使用
const user = await User.create({ /* ... */ });
const post1 = await Post.create({ /* ... */ });
const post2 = await Post.create({ /* ... */ });
await user.addPosts([post1, post2]);
const userPosts = await user.getPosts();</code></pre>
<h3>多对多关系</h3>
<pre><code class="language-javascript">// Post 和 Tag 多对多关系
Post.belongsToMany(Tag, { through: 'PostTags' });
Tag.belongsToMany(Post, { through: 'PostTags' });

// 使用
const post = await Post.create({ /* ... */ });
const tag1 = await Tag.create({ name: 'tech' });
const tag2 = await Tag.create({ name: 'programming' });
await post.addTags([tag1, tag2]);
const postTags = await post.getTags();</code></pre>
<h2>8. 事务管理</h2>
<pre><code class="language-javascript">// 手动管理事务
const transaction = await sequelize.transaction();

try {
  const user = await User.create({
    firstName: 'John',
    lastName: 'Doe'
  }, { transaction });

  await Profile.create({
    userId: user.id,
    bio: 'Hello world'
  }, { transaction });

  await transaction.commit();
} catch (error) {
  await transaction.rollback();
}

// 自动管理事务
const result = await sequelize.transaction(async (t) =&gt; {
  const user = await User.create({
    firstName: 'John',
    lastName: 'Doe'
  }, { transaction: t });

  await Profile.create({
    userId: user.id,
    bio: 'Hello world'
  }, { transaction: t });

  return user;
});</code></pre>
<h2>9. 钩子 (Hooks)</h2>
<pre><code class="language-javascript">User.beforeCreate(async (user, options) =&gt; {
  user.password = await hashPassword(user.password);
});

User.afterCreate(async (user, options) =&gt; {
  await sendWelcomeEmail(user.email);
});

User.beforeUpdate(async (user, options) =&gt; {
  if (user.changed('password')) {
    user.password = await hashPassword(user.password);
  }
});

User.afterDestroy(async (user, options) =&gt; {
  await cleanupUserData(user.id);
});</code></pre>
<h2>10. 作用域 (Scopes)</h2>
<pre><code class="language-javascript">// 定义作用域
User.addScope('active', {
  where: {
    active: true
  }
});

User.addScope('ageGreaterThan', (value) =&gt; {
  return {
    where: {
      age: {
        [Op.gt]: value
      }
    }
  };
});

// 使用作用域
const activeUsers = await User.scope('active').findAll();
const adults = await User.scope({ method: ['ageGreaterThan', 18] }).findAll();

// 默认作用域
User.init({
  // 属性定义
}, {
  defaultScope: {
    where: {
      active: true
    }
  },
  scopes: {
    inactive: {
      where: {
        active: false
      }
    }
  }
});</code></pre>
<h2>11. 原始查询</h2>
<pre><code class="language-javascript">// 不带模型映射的原始查询
const [results, metadata] = await sequelize.query("SELECT * FROM users WHERE age &gt; :age", {
  replacements: { age: 18 },
  type: QueryTypes.SELECT
});

// 带模型映射的原始查询
const users = await sequelize.query("SELECT * FROM users", {
  model: User,
  mapToModel: true
});</code></pre>
<h2>12. 迁移 (Migrations)</h2>
<p>在生产环境中，应该使用迁移而不是 <code>sync()</code> 来管理数据库结构变化。</p>
<h3>安装 Sequelize CLI</h3>
<pre><code class="language-bash">npm install --save-dev sequelize-cli
npx sequelize-cli init</code></pre>
<p>这会创建以下目录结构：</p>
<pre><code>config/
  config.json
migrations/
models/
seeders/</code></pre>
<h3>创建迁移</h3>
<pre><code class="language-bash">npx sequelize-cli migration:generate --name create-users-table</code></pre>
<p>编辑生成的迁移文件：</p>
<pre><code class="language-javascript">module.exports = {
  up: async (queryInterface, Sequelize) =&gt; {
    await queryInterface.createTable('Users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      firstName: {
        type: Sequelize.STRING,
        allowNull: false
      },
      lastName: {
        type: Sequelize.STRING
      },
      age: {
        type: Sequelize.INTEGER,
        defaultValue: 18
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: async (queryInterface, Sequelize) =&gt; {
    await queryInterface.dropTable('Users');
  }
};</code></pre>
<h3>运行迁移</h3>
<pre><code class="language-bash">npx sequelize-cli db:migrate</code></pre>
<h3>回滚迁移</h3>
<pre><code class="language-bash">npx sequelize-cli db:migrate:undo
npx sequelize-cli db:migrate:undo:all</code></pre>
<h2>13. 数据填充 (Seeders)</h2>
<pre><code class="language-bash">npx sequelize-cli seed:generate --name demo-user</code></pre>
<p>编辑生成的种子文件：</p>
<pre><code class="language-javascript">module.exports = {
  up: async (queryInterface, Sequelize) =&gt; {
    await queryInterface.bulkInsert('Users', [{
      firstName: 'John',
      lastName: 'Doe',
      age: 25,
      createdAt: new Date(),
      updatedAt: new Date()
    }, {
      firstName: 'Jane',
      lastName: 'Doe',
      age: 30,
      createdAt: new Date(),
      updatedAt: new Date()
    }], {});
  },
  down: async (queryInterface, Sequelize) =&gt; {
    await queryInterface.bulkDelete('Users', null, {});
  }
};</code></pre>
<p>运行种子：</p>
<pre><code class="language-bash">npx sequelize-cli db:seed:all
npx sequelize-cli db:seed --seed name-of-seed-as-in-data</code></pre>
<h2>14. 性能优化技巧</h2>
<ol>
<li>
<p><strong>批量操作</strong>：使用 <code>bulkCreate</code>、<code>update</code> 和 <code>destroy</code> 进行批量操作</p>
</li>
<li>
<p><strong>选择性查询</strong>：只查询需要的字段</p>
<pre><code class="language-javascript">User.findAll({
 attributes: ['id', 'firstName']
});</code></pre>
</li>
<li>
<p><strong>分页</strong>：使用 <code>limit</code> 和 <code>offset</code> 实现分页</p>
</li>
<li>
<p><strong>预加载关联数据</strong>：使用 <code>include</code> 避免 N+1 查询问题</p>
<pre><code class="language-javascript">User.findAll({
 include: [{
   model: Post,
   include: [Tag]
 }]
});</code></pre>
</li>
<li>
<p><strong>使用索引</strong>：在经常查询的字段上创建索引</p>
</li>
<li>
<p><strong>避免在循环中查询</strong>：尽可能批量查询</p>
</li>
</ol>
<h2>15. 常见问题与解决方案</h2>
<h3>连接池问题</h3>
<p>如果遇到连接池耗尽错误，可以调整连接池配置：</p>
<pre><code class="language-javascript">const sequelize = new Sequelize(/* ... */, {
  pool: {
    max: 20,    // 最大连接数
    min: 0,     // 最小连接数
    acquire: 30000, // 获取连接的最大等待时间(毫秒)
    idle: 10000     // 连接空闲时间(毫秒)，超过此时间连接会被释放
  }
});</code></pre>
<h3>时区问题</h3>
<pre><code class="language-javascript">const sequelize = new Sequelize(/* ... */, {
  dialectOptions: {
    useUTC: false, // 对于MySQL
    timezone: '+08:00' // 设置时区
  },
  timezone: '+08:00' // ORM层时区设置
});</code></pre>
<h3>长连接断开</h3>
<p>对于长时间空闲的连接可能会被数据库服务器断开，可以设置 keep-alive 查询：</p>
<pre><code class="language-javascript">setInterval(() =&gt; {
  sequelize.query('SELECT 1');
}, 60000); // 每分钟执行一次</code></pre>
<h2>16. 最佳实践</h2>
<ol>
<li><strong>使用迁移而不是 sync()</strong>：在生产环境中始终使用迁移来管理数据库结构变更</li>
<li><strong>合理使用事务</strong>：对于需要原子性操作的多个数据库操作使用事务</li>
<li><strong>验证输入数据</strong>：在模型层和业务逻辑层都进行数据验证</li>
<li><strong>处理错误</strong>：妥善处理数据库错误，提供有意义的错误信息</li>
<li><strong>日志记录</strong>：记录重要的数据库操作，但避免记录敏感信息</li>
<li><strong>索引优化</strong>：根据查询模式合理设计索引</li>
<li><strong>定期备份</strong>：即使有迁移脚本，也要定期备份数据库</li>
</ol>
<h2>17. 高级特性</h2>
<h3>多数据库连接</h3>
<pre><code class="language-javascript">const sequelize1 = new Sequelize('database1', 'user', 'pass', { dialect: 'mysql' });
const sequelize2 = new Sequelize('database2', 'user', 'pass', { dialect: 'postgres' });

// 模型可以绑定到不同的 Sequelize 实例
const User = sequelize1.define('User', { /* ... */ });
const Product = sequelize2.define('Product', { /* ... */ });</code></pre>
<h3>读写分离</h3>
<pre><code class="language-javascript">const sequelize = new Sequelize('database', null, null, {
  dialect: 'mysql',
  replication: {
    read: [
      { host: 'read1.example.com', username: 'readuser', password: 'readpass' },
      { host: 'read2.example.com', username: 'readuser', password: 'readpass' }
    ],
    write: { host: 'write.example.com', username: 'writeuser', password: 'writepass' }
  },
  pool: { // 为读写分离配置不同的连接池
    max: 20,
    idle: 30000
  }
});</code></pre>
<h3>乐观锁</h3>
<pre><code class="language-javascript">const User = sequelize.define('User', {
  /* 属性定义 */
}, {
  version: true // 启用乐观锁，添加 version 字段
});

// 更新时会自动检查 version
const user = await User.findByPk(1);
try {
  user.name = 'New Name';
  await user.save(); // 如果 version 不匹配会抛出 OptimisticLockError
} catch (error) {
  if (error instanceof OptimisticLockError) {
    // 处理并发冲突
  }
}</code></pre>
<h2>18. 调试技巧</h2>
<ol>
<li>
<p><strong>启用 SQL 日志</strong>：</p>
<pre><code class="language-javascript">const sequelize = new Sequelize(/* ... */, {
 logging: console.log // 或自定义日志函数
});</code></pre>
</li>
<li>
<p><strong>使用 sequelize-logger</strong>：</p>
<pre><code class="language-bash">npm install sequelize-logger</code></pre>
<pre><code class="language-javascript">const Sequelize = require('sequelize');
require('sequelize-logger')(Sequelize); // 在创建实例前调用</code></pre>
</li>
<li>
<p><strong>性能分析</strong>：</p>
<pre><code class="language-javascript">const { QueryTypes } = require('sequelize');
const start = Date.now();
const users = await User.findAll();
console.log(`Query took ${Date.now() - start}ms`);</code></pre>
</li>
</ol>
<h2>19. 与 Express 集成示例</h2>
<pre><code class="language-javascript">const express = require('express');
const { Sequelize, Op } = require('sequelize');
const app = express();

// 中间件
app.use(express.json());

// 路由
app.get('/users', async (req, res) =&gt; {
  try {
    const users = await User.findAll({
      where: {
        age: {
          [Op.gte]: req.query.minAge || 0
        }
      },
      limit: 100
    });
    res.json(users);
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

app.post('/users', async (req, res) =&gt; {
  try {
    const user = await User.create(req.body);
    res.status(201).json(user);
  } catch (error) {
    console.error(error);
    res.status(400).json({ error: 'Bad request' });
  }
});

// 错误处理
app.use((err, req, res, next) =&gt; {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () =&gt; {
  try {
    await sequelize.authenticate();
    console.log('Database connection has been established successfully.');
    console.log(`Server is running on port ${PORT}`);
  } catch (error) {
    console.error('Unable to connect to the database:', error);
    process.exit(1);
  }
});</code></pre>
<h2>20. 资源与进一步学习</h2>
<ul>
<li><a href="https://sequelize.org/">官方文档</a></li>
<li><a href="https://github.com/sequelize/sequelize">Sequelize GitHub</a></li>
<li><a href="https://sequelize.org/master/manual/recipes.html">Sequelize Cookbook</a></li>
<li><a href="https://github.com/demopark/sequelize-docs-Zh-CN">Sequelize 中文文档</a></li>
</ul>
<p>希望这份详细的 Sequelize 使用笔记对你的开发工作有所帮助！根据实际项目需求，你可以选择适合的功能和配置来优化你的数据库操作。</p>]]></description>
    <pubDate>Sun, 08 Jun 2025 23:04:20 +0800</pubDate>
    <dc:creator>Mr 焦</dc:creator>
    <guid>https://www.mtsws.cn/post-17.html</guid>
</item>
<item>
    <title>Node.js中Joi的详细用法</title>
    <link>https://www.mtsws.cn/post-16.html</link>
    <description><![CDATA[<h1>Joi 在 Node.js 中的详细用法</h1>
<p>Joi 是一个强大的 JavaScript 对象模式验证库，常用于 Node.js 应用中验证和转换数据。下面详细介绍 Joi 的用法。</p>
<h2>安装</h2>
<pre><code class="language-bash">npm install joi</code></pre>
<h2>基本用法</h2>
<h3>1. 基本验证</h3>
<pre><code class="language-javascript">const Joi = require('joi');

// 定义 schema
const schema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),
  password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),
  repeat_password: Joi.ref('password'),
  email: Joi.string().email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } })
}).with('password', 'repeat_password');

// 验证数据
const { error, value } = schema.validate({
  username: 'abc',
  password: 'password123',
  repeat_password: 'password123',
  email: 'abc@example.com'
});

if (error) {
  console.error(error.details);
} else {
  console.log('验证通过', value);
}</code></pre>
<h3>2. 数据类型验证</h3>
<h4>字符串验证</h4>
<pre><code class="language-javascript">Joi.string()
  .alphanum()    // 只允许字母数字字符
  .min(3)        // 最小长度3
  .max(30)       // 最大长度30
  .required()    // 必填字段
  .trim()        // 自动去除前后空格
  .lowercase()   // 转换为小写
  .uppercase()   // 转换为大写
  .regex(/^[a-z]+$/) // 正则匹配</code></pre>
<h4>数字验证</h4>
<pre><code class="language-javascript">Joi.number()
  .integer()     // 必须是整数
  .min(1)        // 最小值1
  .max(10)       // 最大值10
  .precision(2)  // 小数点后2位
  .positive()    // 必须是正数
  .negative()    // 必须是负数</code></pre>
<h4>布尔值验证</h4>
<pre><code class="language-javascript">Joi.boolean()
  .truthy('yes') // 将'yes'视为true
  .falsy('no')   // 将'no'视为false</code></pre>
<h4>日期验证</h4>
<pre><code class="language-javascript">Joi.date()
  .min('1-1-2000')  // 最小日期
  .max('now')       // 最大日期为现在
  .iso()            // ISO格式</code></pre>
<h4>数组验证</h4>
<pre><code class="language-javascript">Joi.array()
  .items(Joi.string(), Joi.number()) // 数组元素可以是字符串或数字
  .length(5)       // 数组长度必须为5
  .min(1)          // 最小长度1
  .max(10)         // 最大长度10
  .unique()        // 元素必须唯一</code></pre>
<h4>对象验证</h4>
<pre><code class="language-javascript">Joi.object({
  name: Joi.string(),
  age: Joi.number()
})
  .and('name', 'age')  // name和age必须同时存在
  .or('name', 'age')   // name或age必须存在一个
  .xor('name', 'age')  // name或age必须存在一个但不能同时存在
  .pattern(/^a/, Joi.string()) // 所有以a开头的属性必须是字符串</code></pre>
<h3>3. 高级验证</h3>
<h4>条件验证</h4>
<pre><code class="language-javascript">const schema = Joi.object({
  isAdmin: Joi.boolean(),
  accessLevel: Joi.when('isAdmin', {
    is: true,
    then: Joi.number().valid(1, 2, 3),
    otherwise: Joi.number().valid(4, 5)
  })
});</code></pre>
<h4>自定义验证</h4>
<pre><code class="language-javascript">const schema = Joi.object({
  password: Joi.string().custom((value, helpers) =&gt; {
    if (value.length &lt; 8) {
      return helpers.error('password.too.short');
    }
    if (!/[A-Z]/.test(value)) {
      return helpers.error('password.no.uppercase');
    }
    return value;
  }, '密码验证')
}).messages({
  'password.too.short': '密码长度至少8个字符',
  'password.no.uppercase': '密码必须包含至少一个大写字母'
});</code></pre>
<h4>引用其他字段值</h4>
<pre><code class="language-javascript">const schema = Joi.object({
  password: Joi.string().required(),
  confirmPassword: Joi.string().valid(Joi.ref('password')).required()
});</code></pre>
<h3>4. 错误处理</h3>
<pre><code class="language-javascript">const { error, value } = schema.validate(data, {
  abortEarly: false, // 不遇到第一个错误就停止，收集所有错误
  allowUnknown: true, // 允许未知键
  stripUnknown: true, // 移除未知键
  convert: true      // 尝试类型转换
});

if (error) {
  // 错误详情
  error.details.forEach((detail) =&gt; {
    console.log(detail.message);
    console.log(detail.path); // 错误路径
    console.log(detail.type); // 错误类型
  });

  // 自定义错误消息
  const errors = error.details.map(detail =&gt; ({
    field: detail.path.join('.'),
    message: detail.message
  }));
}</code></pre>
<h3>5. 扩展和自定义</h3>
<h4>创建自定义验证规则</h4>
<pre><code class="language-javascript">const customJoi = Joi.extend((joi) =&gt; {
  return {
    type: 'string',
    base: joi.string(),
    messages: {
      'string.oddLength': '{{#label}} 长度必须是奇数',
    },
    validate(value, helpers) {
      if (value.length % 2 === 0) {
        return { value, errors: helpers.error('string.oddLength') };
      }
    }
  };
});

const schema = customJoi.string().oddLength();</code></pre>
<h3>6. 实用方法</h3>
<h4>默认值</h4>
<pre><code class="language-javascript">Joi.string().default('default value')</code></pre>
<h4>可选/必填</h4>
<pre><code class="language-javascript">Joi.string().optional()
Joi.string().required()</code></pre>
<h4>允许/禁止值</h4>
<pre><code class="language-javascript">Joi.string().valid('value1', 'value2') // 只允许特定值
Joi.string().invalid('value1', 'value2') // 禁止特定值</code></pre>
<h4>标签和描述</h4>
<pre><code class="language-javascript">Joi.string().label('用户名').description('用户的登录名')</code></pre>
<h3>7. 数组和对象的高级用法</h3>
<h4>数组元素验证</h4>
<pre><code class="language-javascript">Joi.array().items(
  Joi.object({
    id: Joi.number().required(),
    name: Joi.string().required()
  })
)</code></pre>
<h4>对象模式匹配</h4>
<pre><code class="language-javascript">Joi.object({
  a: Joi.string(),
  b: Joi.number()
}).pattern(Joi.string().pattern(/^x/), Joi.boolean())</code></pre>
<h3>8. 异步验证</h3>
<pre><code class="language-javascript">const schema = Joi.object({
  username: Joi.string().external(async (value, helpers) =&gt; {
    const exists = await checkUsernameExists(value);
    if (exists) {
      return helpers.error('username.exists');
    }
    return value;
  })
});

try {
  const value = await schema.validateAsync({ username: 'test' });
} catch (err) {
  console.error(err);
}</code></pre>
<h2>实际应用示例</h2>
<h3>用户注册验证</h3>
<h3>分页查询参数验证</h3>
<pre><code class="language-javascript">const userSchema = Joi.object({
  username: Joi.string()
    .alphanum()
    .min(3)
    .max(30)
    .required()
    .label('用户名')
    .messages({
      'string.empty': '用户名不能为空',
      'string.min': '用户名长度不能少于3个字符',
      'string.max': '用户名长度不能超过30个字符'
    }),

  password: Joi.string()
    .pattern(new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&amp;*])(?=.{8,})'))
    .required()
    .label('密码')
    .messages({
      'string.pattern.base': '密码必须包含大小写字母、数字和特殊字符，且长度至少8位'
    }),

  email: Joi.string()
    .email({ minDomainSegments: 2 })
    .required()
    .label('电子邮箱'),

  birth_year: Joi.number()
    .integer()
    .min(1900)
    .max(2023)
    .label('出生年份'),

  roles: Joi.array()
    .items(Joi.string().valid('user', 'admin', 'editor'))
    .default(['user'])
    .label('角色'),

  metadata: Joi.object()
    .unknown()
    .label('元数据'),

  createdAt: Joi.date()
    .default(Date.now)
    .label('创建时间')
}).options({ abortEarly: false });</code></pre>
<pre><code class="language-javascript">const paginationSchema = Joi.object({
  page: Joi.number()
    .integer()
    .min(1)
    .default(1)
    .label('页码'),

  pageSize: Joi.number()
    .integer()
    .min(1)
    .max(100)
    .default(10)
    .label('每页数量'),

  sort: Joi.string()
    .valid('asc', 'desc')
    .default('desc')
    .label('排序方式'),

  search: Joi.string()
    .trim()
    .max(100)
    .allow('')
    .label('搜索关键词')
});</code></pre>
<h2>最佳实践</h2>
<ol>
<li><strong>复用验证模式</strong>：将常用的验证模式提取为可复用的组件</li>
<li><strong>详细错误消息</strong>：提供有意义的错误消息</li>
<li><strong>尽早验证</strong>：在请求处理流程的早期进行验证</li>
<li><strong>区分开发和生产</strong>：在生产环境中可能不需要返回详细的验证错误</li>
<li><strong>结合Express中间件</strong>：创建验证中间件</li>
</ol>
<pre><code class="language-javascript">function validate(schema, property = 'body') {
  return (req, res, next) =&gt; {
    const { error, value } = schema.validate(req[property], {
      abortEarly: false,
      allowUnknown: false
    });

    if (error) {
      const errors = error.details.map(detail =&gt; ({
        field: detail.path.join('.'),
        message: detail.message
      }));
      return res.status(400).json({ errors });
    }

    req[property] = value;
    next();
  };
}

// 使用示例
router.post('/users', validate(userSchema), userController.create);</code></pre>
<p>Joi 提供了强大而灵活的验证功能，可以帮助你确保应用程序数据的完整性和一致性。</p>]]></description>
    <pubDate>Sun, 08 Jun 2025 21:47:06 +0800</pubDate>
    <dc:creator>Mr 焦</dc:creator>
    <guid>https://www.mtsws.cn/post-16.html</guid>
</item>
<item>
    <title>Expo Router的核心用法汇总</title>
    <link>https://www.mtsws.cn/post-15.html</link>
    <description><![CDATA[<p>以下是 <strong>Expo Router</strong> 的核心用法汇总，涵盖路由配置、导航、参数传递等关键功能（基于 Expo SDK 49+）：</p>
<hr />
<h3>🗺️ 路由配置（文件系统路由）</h3>
<pre><code class="language-bash">app/
├── (tabs)/          # 嵌套布局组
│   ├── index.js     # /tabs/
│   └── [user].js    # /tabs/{user}
├── _layout.js       # 全局布局
├── index.js         # 首页 (/)
├── search/          
│   ├── index.js     # /search
│   └── [term].js    # /search/{term}
└── modal.js         # 模态屏 (/modal)</code></pre>
<hr />
<h3>🔄 核心导航方法</h3>
<pre><code class="language-jsx">import { Link, router, useRouter } from 'expo-router';

// 1. 组件导航
&lt;Link href="/profile"&gt;前往个人页&lt;/Link&gt;

// 2. 编程式导航
router.push('/settings');      // 跳转
router.replace('/login');      // 替换当前路由
router.back();                 // 返回

// 3. 获取路由对象
const { push, back, replace } = useRouter();</code></pre>
<hr />
<h3>📌 动态路由参数</h3>
<p><strong>文件命名</strong>：<code>[param].js</code><br />
<strong>获取参数</strong>：</p>
<pre><code class="language-jsx">// app/user/[id].js
import { useLocalSearchParams } from 'expo-router';

export default function User() {
  const { id } = useLocalSearchParams(); // 获取 /user/123 中的 id
  return &lt;Text&gt;用户ID: {id}&lt;/Text&gt;;
}</code></pre>
<hr />
<h3>🖼️ 布局系统</h3>
<p><strong>全局布局</strong> (<code>app/_layout.js</code>)：</p>
<pre><code class="language-jsx">export default function Layout() {
  return (
    &lt;Stack&gt;
      &lt;Stack.Screen name="index" options={{ title: '首页' }} /&gt;
      &lt;Stack.Screen name="profile" options={{ headerShown: false }} /&gt;
    &lt;/Stack&gt;
  );
}</code></pre>
<p><strong>嵌套布局</strong>：</p>
<pre><code class="language-jsx">// app/(tabs)/_layout.js
export default function TabsLayout() {
  return (
    &lt;Tabs&gt;
      &lt;Tabs.Screen name="home" /&gt;
      &lt;Tabs.Screen name="settings" /&gt;
    &lt;/Tabs&gt;
  );
}</code></pre>
<hr />
<h3>🔐 路由守卫（认证控制）</h3>
<pre><code class="language-jsx">// app/_layout.js
import { Redirect } from 'expo-router';

export default function RootLayout() {
  const { isAuthenticated } = useAuth();

  if (!isAuthenticated) {
    return &lt;Redirect href="/login" /&gt;;
  }

  return &lt;Stack /&gt;;
}</code></pre>
<hr />
<h3>🌐 深层链接配置</h3>
<ol>
<li>
<p><strong>配置 <code>app.json</code></strong>：</p>
<pre><code class="language-json">{
"expo": {
"scheme": "myapp",
"plugins": [
  ["expo-router", { "origin": "https://yourapp.com" }]
]
}
}</code></pre>
</li>
<li>
<p><strong>处理链接</strong>：</p>
<pre><code class="language-jsx">
// app/index.js
import { useUrl } from 'expo-router';</code></pre>
</li>
</ol>
<p>export default function Home() {<br />
const url = useUrl(); // 获取 deep link 参数<br />
// 处理类似 myapp://user/123 的链接<br />
}</p>
<pre><code>
---

### 🧩 高级路由操作
```jsx
// 1. 传递对象参数
router.push({
  pathname: '/user',
  params: { id: 123, name: 'Alice' }
});

// 2. 预加载路由
import { usePreload } from 'expo-router';
usePreload('/heavy-screen'); // 提前加载资源

// 3. 监听路由事件
import { useFocusEffect } from 'expo-router';
useFocusEffect(() =&gt; {
  console.log('屏幕已聚焦');
});</code></pre>
<hr />
<h3>🧭 路由类型（TypeScript）</h3>
<pre><code class="language-typescript">// 定义路由参数类型
export type RootStackParamList = {
  index: undefined;
  profile: { userId: string };
};

// 在组件中使用
const { userId } = useLocalSearchParams&lt;{ userId: string }&gt;();</code></pre>
<hr />
<h3>⚠️ 注意事项</h3>
<ol>
<li>
<p><strong>文件约定</strong>：</p>
<ul>
<li><code>index.js</code> 代表目录默认路由</li>
<li><code>_layout.js</code> 为布局文件</li>
<li><code>(group)</code> 目录名用于逻辑分组（不显示在URL）</li>
</ul>
</li>
<li>
<p><strong>路由行为</strong>：</p>
<ul>
<li>所有路由默认使用 <strong>栈导航</strong>（可配置为 Tabs/Drawer）</li>
<li>路径匹配规则：<code>app/**/*.js</code> → <code>/path</code></li>
</ul>
</li>
<li>
<p><strong>开发工具</strong>：</p>
<pre><code class="language-bash">npx expo start    # 启动开发服务器
Ctrl + R          # 查看路由调试面板</code></pre>
</li>
</ol>
<blockquote>
<p>官方文档：<a href="https://docs.expo.dev/routing/">Expo Router Docs</a><br />
示例项目：<a href="https://github.com/expo/router-example">GitHub - expo/router-example</a></p>
</blockquote>
<hr />
<h3>💡 实用代码片段</h3>
<p><strong>模态屏实现</strong>：</p>
<pre><code class="language-jsx">// app/modal.js
export default function Modal() {
  return (
    &lt;View style={{ flex: 1 }}&gt;
      &lt;Text&gt;模态内容&lt;/Text&gt;
      &lt;Link href="../"&gt;关闭&lt;/Link&gt; {/* 返回上级 */}
    &lt;/View&gt;
  );
}</code></pre>
<p><strong>强制横屏页面</strong>：</p>
<pre><code class="language-jsx">// app/game/_layout.js
import * as ScreenOrientation from 'expo-screen-orientation';

export default function GameLayout() {
  useLayoutEffect(() =&gt; {
    ScreenOrientation.lockAsync(
      ScreenOrientation.OrientationLock.LANDSCAPE
    );
    return () =&gt; ScreenOrientation.unlockAsync();
  }, []);

  return &lt;Stack /&gt;;
}</code></pre>]]></description>
    <pubDate>Fri, 30 May 2025 14:05:28 +0800</pubDate>
    <dc:creator>Mr 焦</dc:creator>
    <guid>https://www.mtsws.cn/post-15.html</guid>
</item>
<item>
    <title>动态引入svg失败，改用img实现gallery</title>
    <link>https://www.mtsws.cn/post-14.html</link>
    <description><![CDATA[<p>在nextjs项目中，有/public/images/icon目录，其中有很多svg图标。我希望做一个gallery页面，展示这些svg，但不能使用![]()元素，因为fill和stroke属性会无法生效，一些没有任何颜色的svg会显示不出来。</p>
<p>项目在next.config.js中配置了@svgr/webpack</p>
<pre><code>{
  test: /\.svg$/i,
  issuer: /\.[jt]sx?$/,
  use: [
    {
      loader: '@svgr/webpack',
      options: {
        typescript: true,
        icon: true
      }
    }
  ]
}</code></pre>
<p>服务端组件引入svg会报错</p>
<pre><code>// Module parse failed: Unexpected token (1:0)
// You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.
import BanIcon from 'public/images/icon/insights/ban.svg'</code></pre>
<p>use client + 动态引入，不行</p>
<pre><code>'use client'
import dynamic from 'next/dynamic';

export default async function ClientSvg(path) {
  // 成功加载
  const Svg = dynamic(() =&gt; import('public/images/icon/menu/icon_topview.svg'), { ssr: false }) 

  // 失败的加载，将path整个传入
  // Cannot find module 'public/images/icon/menu/icon_topview.svg'
  const Svg = dynamic(() =&gt; import(path), { ssr: false }) 

  // 失败的加载，将path部分传入
  // 虽然配置了webpack-loader，但无效
  // Module parse failed: Unexpected token (1:0)
  // You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.
  const Svg = dynamic(() =&gt; import('public/images/icon/menu/${path}.svg'), { ssr: false }) 

  return &lt;p&gt;

  &lt;/p&gt;
}</code></pre>
<p>有没有webpack魔法注释可以解决上面的loader问题？没有</p>
<p>尝试另一种引入方法，无效</p>
<pre><code>const svgDir = require.context("!@svgr/webpack!../../assets/svg/");
const Icon = svgDir("./Group 9.svg").default</code></pre>
<p>目前仍然找不到合适的方法动态引入svg</p>
<h2>转换思路 </h2>
<p>使用 img 标签来展示 svg 图标，对于没有颜色的 svg 无法显示的问题，只需要对 svg 设置 <code>fill="currentColor" stroke="currentColor"</code> 即可在 img 标签中渲染出有颜色的图案。</p>
<p>详细代码： <a href="https://gist.github.com/Jiny3213/8f6fbcb1f04299fee58ad1036a7247a9">https://gist.github.com/Jiny3213/8f6fbcb1f04299fee58ad1036a7247a9</a></p>]]></description>
    <pubDate>Fri, 30 May 2025 09:26:36 +0800</pubDate>
    <dc:creator>Mr 焦</dc:creator>
    <guid>https://www.mtsws.cn/post-14.html</guid>
</item>
<item>
    <title>Linux 系统-常见目录</title>
    <link>https://www.mtsws.cn/post-12.html</link>
    <description><![CDATA[<h1><a href="/DevOps/Linux/common-directory#%E5%B8%B8%E8%A7%81%E7%9B%AE%E5%BD%95"></a>常见目录</h1>
<p>Linux 将整个文件系统看作一棵树，这棵树的树根叫做根文件系统，用 <code>/</code> 表示。</p>
<h2><a href="/DevOps/Linux/common-directory#%E5%B8%B8%E7%94%A8%E7%9A%84%E7%B3%BB%E7%BB%9F%E6%96%87%E4%BB%B6%E7%9B%AE%E5%BD%95"></a>常用的系统文件目录</h2>
<p><a href="https://www.mtsws.cn/content/uploadfile/202505/e33f1748567837.png"><img src="https://www.mtsws.cn/content/uploadfile/202505/e33f1748567837.png" alt="" /></a></p>
<h2><a href="/DevOps/Linux/common-directory#%E5%85%B6%E4%BB%96%E6%96%87%E4%BB%B6%E7%9B%AE%E5%BD%95"></a>其他文件目录</h2>
<p>目录语义描述/bootBoot Loader Files启动 Linux 时的核心文件/devDevice Files所有 Linux 的外围设备/lost+found无家可归文件的避难所/mntMount Directory空目录，用于提供给用户临时挂接别的文件系统/optOptional add-on Apps第三方工具使用的安装目录/srvService Data/mediaRemovable Devices</p>
<h2><a href="/DevOps/Linux/common-directory#%E8%99%9A%E6%8B%9F%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F"></a>虚拟文件系统</h2>
<p><code>/proc</code> 目录挂载了一个<strong>虚拟文件系统</strong>，以<strong>虚拟文件</strong>的形式映射系统与进程在内存中的运行时信息。</p>
<h3><a href="/DevOps/Linux/common-directory#%E7%B3%BB%E7%BB%9F%E4%BF%A1%E6%81%AF"></a>系统信息</h3>
<p><code>/proc</code> 下的直接子目录通常存储系统信息。</p>
<p>目录描述举例/proc/cpuinfo处理器的相关信息physical id、cpu cores、siblings、processor/proc/version系统的内核版本号Linux version 3.10.</p>
<h3><a href="/DevOps/Linux/common-directory#%E8%BF%9B%E7%A8%8B%E4%BF%A1%E6%81%AF"></a>进程信息</h3>
<p>重点是 <code>/proc/</code> 目录映射的进程信息。以</p>
<p>目录描述<code>/proc//cmdline</code>启动当前进程的完整命令<code>/proc//cwd</code>当前进程工作目录的软链<code>/proc//environ</code>当前进程的环境变量列表<code>/proc//exe</code>启动当前进程的可执行文件的软链<code>/proc//fd</code>目录，保持当前进程持有的文件描述符（以软链形式存在，指向实际文件）<code>/proc//limits</code>当前进程使用资源的软限制、硬限制（和单位）<code>/proc/task</code>目录，保存当前进程所运行的每一个线程的相关信息；以 <code>` 作为各线程的目录名，目录结构与</code>/proc/` 相似</p>
<h2><a href="/DevOps/Linux/common-directory#%E6%95%B0%E6%8D%AE%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F"></a>数据文件系统</h2>
<p><code>/var</code> 目录存放数据文件，如程序数据、日志等；但线上通常只将日志放在 <code>/var</code> 目录。</p>
<p>通过 rsyslog 记录系统级日志，配置文件为 <code>/etc/rsyslog.conf</code>。重点看 <code>/var/log/messages</code> 的配置：</p>
<pre><code>&lt;p&gt;# Log anything (except mail) of level info or higher.&lt;/p&gt;&lt;p&gt;# Don't log private authentication messages!&lt;/p&gt;&lt;p&gt;*.info;mail.none;authpriv.none;cron.none                /var/log/messages&lt;/p&gt;</code></pre>
<blockquote>
<p><code>*.info</code>表示所有服务大于等于 info 优先级的信息都会记录到 <code>/var/log/messages</code> 中； <code>mail.none</code> 表示不记录任何 mail 的信息到 <code>/var/log/messages</code> 中。</p>
</blockquote>
<p>以上配置表示：<strong>除安全认证、邮件、定时任务外，输出到 stdout、stderr 的 info 及更高级别的日志记录在 <code>/var/log/messages</code> 中</strong>。</p>
<hr />
<p><strong>参考资料：</strong></p>
<p><a href="https://ossbymanu.blogspot.com/2012/02/file-structure-in-linux.html">📝 File Structure in Linux</a><a href="https://juejin.im/post/5aaf1975f265da239d4918b9">📝 Linux 文件系统目录结构</a><a href="/DevOps/Linux/%5Bhttps://monkeysayhi.github.io/2017/11/29/%E6%B5%85%E8%B0%88linux%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B%E5%92%8C%E7%BA%BF%E7%A8%8B%E5%88%87%E6%8D%A2/%5D(https://monkeysayhi.github.io/2017/11/29/%E6%B5%85%E8%B0%88linux%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B%E5%92%8C%E7%BA%BF%E7%A8%8B%E5%88%87%E6%8D%A2/)">浅谈 Linux 线程模型和线程切换</a></p>]]></description>
    <pubDate>Fri, 30 May 2025 09:14:13 +0800</pubDate>
    <dc:creator>Mr 焦</dc:creator>
    <guid>https://www.mtsws.cn/post-12.html</guid>
</item>
<item>
    <title>Linux 系统-基本概要</title>
    <link>https://www.mtsws.cn/post-11.html</link>
    <description><![CDATA[<h1><a href="/DevOps/Linux/linux#%E5%9F%BA%E6%9C%AC%E6%A6%82%E8%A6%81"></a>基本概要</h1>
<p><img src="https://www.jiaoxiaoyuan.cn/static/linux-perf-tools-full.fb52c098.fb52c098.png" alt="Linux Performance Tools" /></p>
<h2><a href="/DevOps/Linux/linux#%E6%8A%BD%E8%B1%A1%E7%BA%A7%E5%88%AB%E5%92%8C%E5%B1%82%E6%AC%A1"></a>抽象级别和层次</h2>
<p>最底层是硬件系统，包括内存和中央处理器（用于计算和从内存中读写数据），此外硬盘和网络接口也是硬件系统的一部分。</p>
<p>硬件系统之上是 <strong>内核</strong>，它是操作系统的核心。内核是运行在内存中的软件，它向中央处理器发送指令。内核管理硬件系统，是硬件系统和应用程序之间进行通信的接口。</p>
<p><strong>进程</strong> 是指计算机中运行的所有程序，由内核统一管理，它们组成了最顶层，称为 <strong>用户空间</strong>。</p>
<p><img src="https://www.jiaoxiaoyuan.cn/static/linux-level.8ed452ac.8ed452ac.png" alt="Linux Level" /></p>
<p>内核和用户进程之间最主要的区别是：内核在 <strong>内核模式</strong>（kernel mode）中运行，而用户进程则在 <strong>用户模式</strong>（user mode）中运行。在内核模式中运行的代码可以不受限地访问中央处理器和内存，这种模式功能强大，但也非常危险，因为内核进程可以轻而易举地使整个系统崩溃。那些只有内核可以访问的空间我们称为 <strong>内核空间</strong>（kernel space）。</p>
<h2><a href="/DevOps/Linux/linux#%E7%A1%AC%E4%BB%B6%E7%B3%BB%E7%BB%9F"></a>硬件系统</h2>
<p><strong>主内存</strong>（main memory）或许是所有硬件系统中最为重要的部分。基本上来讲，主内存存储 <code>0</code> 和 <code>1</code> 这样的数据。我们将每个 <code>0</code> 和 <code>1</code> 称为一个比特（或位，bit）。内核和进程就在主内存中运行，它们就是一系列比特的大合集。所有外围设备的数据输入和输出都通过主内存完成，同样是以一系列 <code>0</code> 和 <code>1</code> 的形式。中央处理器像一个操作员一样处理内存中的数据，它从内存读取指令和数据，然后将运算结果写回内存。</p>
<h2><a href="/DevOps/Linux/linux#%E5%86%85%E6%A0%B8"></a>内核</h2>
<p>Linux 系统的核心是 <strong>内核</strong>。内核控制着计算机系统上的所有硬件和软件，在必要时分配硬件，并根据需要执行软件。</p>
<p>内核主要负责管理以下四种功能：</p>
<p>进程管理：内核决定哪个进程可以使用 CPU内存管理：内核管理所有的内存，为进程分配内存，管理进程间的共享内存以及空闲内存设备驱动程序和设备管理：作为硬件系统（如磁盘）和进程之间的接口，内核负责操控硬件设备系统调用和系统支持：进程通常使用系统调用和内核进行通信</p>
<h3><a href="/DevOps/Linux/linux#%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E5%92%8C%E7%B3%BB%E7%BB%9F%E6%94%AF%E6%8C%81"></a>系统调用和系统支持</h3>
<p>内核还对用户进程提供其他功能。例如，<strong>系统调用</strong>（system call 或 syscall）为进程执行一些它们不擅长或无法完成的工作。打开、读取和写文件这些操作都涉及系统调用。</p>
<p><code>fork</code>：<code>exec</code>：</p>
<h3><a href="/DevOps/Linux/linux#%E7%B3%BB%E7%BB%9F%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86"></a>系统内存管理</h3>
<p>操作系统内核的主要功能之一就是内存管理。内核不仅管理服务器上的可用物理内存，还可以创建和管理虚拟内存（即实际并不存在的内存）。</p>
<p>内核通过硬盘上的存储空间来实现虚拟内存，这块区域称为 <strong>交换空间</strong>（swap space）。内核不断在交换空间和实际的物理内存之间反复交换虚拟内存中的内容。</p>
<h3><a href="/DevOps/Linux/linux#%E8%BD%AF%E4%BB%B6%E7%A8%8B%E5%BA%8F%E7%AE%A1%E7%90%86"></a>软件程序管理</h3>
<p>Linux 操作系统将运行中的程序称为进程。</p>
<p>内核创建了第一个进程（称为 <code>init</code> 进程）来启动系统上所有其他进程。当内核启动时，它会将 <code>init</code> 进程加载到虚拟内存中。内核在启动任何其他进程时，都会在虚拟内存中给新进程分配一块专有区域来存储该进程用到的数据和代码。</p>
<h3><a href="/DevOps/Linux/linux#%E7%A1%AC%E4%BB%B6%E8%AE%BE%E5%A4%87%E7%AE%A1%E7%90%86"></a>硬件设备管理</h3>
<p>任何 Linux 系统需要与之通信的设备，都需要在内核代码中加入其驱动程序代码。驱动程序代码相当于应用程序和硬件设备的中间人，允许内核与设备之间交换数据。在 Linux 内核中有两种方法用于插入设备驱动代码：</p>
<p>编译进内核的设备驱动代码可插入内核的设备驱动模块<br />
Linux 系统将硬件设备当成特殊的文件，称为设备文件。设备文件有三种分类：</p>
<p>字符型设备文件：指处理数据时每次只能处理一个字符的设备（大多数类型的调制解调器和终端都是作为字符型设备文件创建）；块设备文件：指处理数据时每次能处理大块数据的设备，比如硬盘；网络设备文件：指采用数据包发送和接收数据的设备，包括各种网卡和一个特殊的回环设备。这个回环设备允许 Linux 系统使用常见的网络编程协议同自身通信。<br />
Linux 为系统上的每个设备都创建一种称为 <strong>节点</strong> 的特殊文件。与设备的所有通信都通过设备节点完成。每个节点都有唯一的数值对供 Linux 内核标识它。数值对包括一个主设备号和一个次设备号。类似的设备被划分到同样的主设备号下。次设备号用于标识主设备组下的某个特定设备。</p>
<h3><a href="/DevOps/Linux/linux#%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E7%AE%A1%E7%90%86"></a>文件系统管理</h3>
<p>Linux 内核支持通过不同类型的文件系统从硬盘中读写数据。</p>
<p>Linux 内核采用虚拟文件系统（Virtual File System，VFS）作为和每个文件系统交互的接口。这为 Linux 内核同任何类型文件系统通信提供了一个标准接口。当每个文件系统都被挂载和使用时，VFS 将信息都缓存在内存中。</p>]]></description>
    <pubDate>Fri, 30 May 2025 09:09:49 +0800</pubDate>
    <dc:creator>Mr 焦</dc:creator>
    <guid>https://www.mtsws.cn/post-11.html</guid>
</item>
<item>
    <title>react native 语音转文字</title>
    <link>https://www.mtsws.cn/post-9.html</link>
    <description><![CDATA[<p>在 React Native 中实现语音转文字（Speech-to-Text）功能，可以通过以下几种方式来实现。以下是详细的实现步骤和说明：</p>
<hr />
<h3><strong>1. 使用 React Native 社区库</strong></h3>
<p>React Native 社区提供了许多开源库来处理语音识别功能，其中最常用的是 <code>react-native-voice</code> 和 <code>@react-native-community/voice</code>。</p>
<h4><strong>安装依赖</strong></h4>
<p>首先安装 <code>@react-native-community/voice</code> 库：</p>
<pre><code class="language-bash">npm install @react-native-community/voice</code></pre>
<p>或者使用 Yarn：</p>
<pre><code class="language-bash">yarn add @react-native-community/voice</code></pre>
<p>如果你使用的是 React Native 0.60 及以上版本，库会自动链接。如果低于该版本，需要手动链接：</p>
<pre><code class="language-bash">react-native link @react-native-community/voice</code></pre>
<p>对于 iOS，还需要额外配置：</p>
<ul>
<li>
<p>打开 Xcode 项目，在 <code>Info.plist</code> 文件中添加以下权限声明：</p>
<pre><code class="language-xml">&lt;key&gt;NSSpeechRecognitionUsageDescription&lt;/key&gt;
&lt;string&gt;我们需要您的许可来使用语音识别功能&lt;/string&gt;
&lt;key&gt;NSMicrophoneUsageDescription&lt;/key&gt;
&lt;string&gt;我们需要访问麦克风以录制您的语音&lt;/string&gt;</code></pre>
</li>
</ul>
<p>对于 Android，在 <code>AndroidManifest.xml</code> 中添加以下权限：</p>
<pre><code class="language-xml">&lt;uses-permission android:name="android.permission.RECORD_AUDIO" /&gt;</code></pre>
<h4><strong>代码实现</strong></h4>
<p>以下是一个简单的示例代码，展示如何使用 <code>@react-native-community/voice</code> 实现语音转文字功能：</p>
<pre><code class="language-javascript">import React, { useEffect, useState } from 'react';
import { Button, Text, View, PermissionsAndroid, Platform } from 'react-native';
import Voice from '@react-native-community/voice';

const App = () =&gt; {
  const [recognizedText, setRecognizedText] = useState('');
  const [isListening, setIsListening] = useState(false);

  // 请求权限
  const requestAudioPermission = async () =&gt; {
    if (Platform.OS === 'android') {
      try {
        const granted = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
          {
            title: 'Microphone Permission',
            message: 'App needs access to your microphone to recognize speech.',
            buttonNeutral: 'Ask Me Later',
            buttonNegative: 'Cancel',
            buttonPositive: 'OK',
          }
        );
        return granted === PermissionsAndroid.RESULTS.GRANTED;
      } catch (err) {
        console.warn(err);
        return false;
      }
    }
    return true; // iOS 不需要额外请求权限
  };

  // 初始化语音识别事件监听器
  useEffect(() =&gt; {
    Voice.onSpeechStart = onSpeechStart;
    Voice.onSpeechRecognized = onSpeechRecognized;
    Voice.onSpeechEnd = onSpeechEnd;
    Voice.onSpeechError = onSpeechError;
    Voice.onSpeechResults = onSpeechResults;

    return () =&gt; {
      Voice.destroy().then(Voice.removeAllListeners);
    };
  }, []);

  // 开始语音识别
  const startSpeechToText = async () =&gt; {
    const isPermissionGranted = await requestAudioPermission();
    if (!isPermissionGranted) {
      console.log('录音权限未授予');
      return;
    }

    try {
      await Voice.start('zh-CN'); // 设置语言为中文（可根据需求修改）
      setIsListening(true);
    } catch (e) {
      console.error(e);
    }
  };

  // 停止语音识别
  const stopSpeechToText = async () =&gt; {
    try {
      await Voice.stop();
      setIsListening(false);
    } catch (e) {
      console.error(e);
    }
  };

  // 事件处理函数
  const onSpeechStart = () =&gt; {
    console.log('语音识别开始');
  };

  const onSpeechRecognized = () =&gt; {
    console.log('语音被识别');
  };

  const onSpeechEnd = () =&gt; {
    console.log('语音识别结束');
    setIsListening(false);
  };

  const onSpeechError = (e) =&gt; {
    console.error('语音识别错误:', e);
  };

  const onSpeechResults = (e) =&gt; {
    console.log('语音识别结果:', e.value[0]);
    setRecognizedText(e.value[0]); // 获取第一个识别结果
  };

  return (
    &lt;View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}&gt;
      &lt;Text style={{ fontSize: 20, marginBottom: 20 }}&gt;识别结果: {recognizedText}&lt;/Text&gt;
      &lt;Button
        title={isListening ? '停止录音' : '开始录音'}
        onPress={isListening ? stopSpeechToText : startSpeechToText}
      /&gt;
    &lt;/View&gt;
  );
};

export default App;</code></pre>
<hr />
<h3><strong>2. 使用云服务 API</strong></h3>
<p>如果需要更高的准确率或支持更多语言，可以使用第三方云服务（如 Google Cloud Speech-to-Text、Microsoft Azure Speech Service 或百度语音识别等）。</p>
<h4><strong>步骤概述</strong></h4>
<ol>
<li>注册并获取云服务的 API 密钥。</li>
<li>使用 <code>react-native-sound-recorder</code> 或类似库录制音频。</li>
<li>将录制的音频文件上传到云服务，并通过 API 获取识别结果。</li>
</ol>
<h4><strong>示例：使用 Google Cloud Speech-to-Text</strong></h4>
<ol>
<li>
<p>安装录音库：</p>
<pre><code class="language-bash">npm install react-native-sound-recorder</code></pre>
</li>
<li>
<p>配置 Google Cloud Speech-to-Text API 并上传音频文件。</p>
</li>
<li>
<p>解析返回的 JSON 数据，提取识别结果。</p>
</li>
</ol>
<p>由于此方法涉及较多后端逻辑，适合有一定开发经验的开发者。</p>
<hr />
<h3><strong>3. 注意事项</strong></h3>
<ul>
<li><strong>设备兼容性</strong>：不同设备对语音识别的支持可能有所不同，建议在主流设备上进行测试。</li>
<li><strong>权限管理</strong>：确保正确处理录音权限，尤其是在 Android 和 iOS 上。</li>
<li><strong>语言设置</strong>：根据用户需求选择合适的语言代码（如 <code>en-US</code> 表示美式英语，<code>zh-CN</code> 表示简体中文）。</li>
</ul>
<hr />
<h3><strong>总结</strong></h3>
<p>对于大多数应用场景，推荐使用 <code>@react-native-community/voice</code> 库，因为它简单易用且支持本地语音识别。如果需要更高精度或跨平台支持，可以选择云服务 API。希望上述内容能帮助你顺利实现语音转文字功能！如果有进一步的问题，请随时提问！</p>
<p>要将百度语音识别服务与 React Native 配合使用，可以按照以下步骤实现。百度语音识别提供了强大的语音转文字功能，并且支持多种语言和场景。以下是详细的实现步骤：</p>
<hr />
<h3><strong>1. 准备工作</strong></h3>
<h4><strong>注册百度云账号并获取 API 密钥</strong></h4>
<ol>
<li>访问 <a href="https://cloud.baidu.com/">百度智能云</a> 并注册账号。</li>
<li>创建一个语音识别应用，获取 <code>API Key</code> 和 <code>Secret Key</code>。</li>
<li>下载百度语音 SDK（可选），但这里我们主要通过 RESTful API 实现。</li>
</ol>
<hr />
<h3><strong>2. 安装依赖</strong></h3>
<p>为了实现录音和音频处理，我们需要安装以下依赖：</p>
<ul>
<li><code>react-native-sound-recorder</code>：用于录制音频。</li>
<li><code>axios</code> 或 <code>fetch</code>：用于发送 HTTP 请求到百度语音识别 API。</li>
</ul>
<pre><code class="language-bash">npm install react-native-sound-recorder axios</code></pre>
<p>如果你使用的是 React Native 0.60 及以上版本，库会自动链接。如果低于该版本，需要手动链接。</p>
<hr />
<h3><strong>3. 录制音频</strong></h3>
<p>使用 <code>react-native-sound-recorder</code> 录制音频文件。百度语音识别支持 PCM 格式的音频文件。</p>
<pre><code class="language-javascript">import SoundRecorder from 'react-native-sound-recorder';

const startRecording = async () =&gt; {
  try {
    await SoundRecorder.start(SoundRecorder.PATH_CACHE + '/audio.pcm', {
      format: 'pcm',
      encoder: 'pcm',
      channels: 1, // 单声道
      sampleRate: 16000, // 百度语音识别要求的采样率
    });
    console.log('开始录音');
  } catch (error) {
    console.error('录音失败:', error);
  }
};

const stopRecording = async () =&gt; {
  try {
    const filePath = await SoundRecorder.stop();
    console.log('录音结束，文件路径:', filePath);
    return filePath;
  } catch (error) {
    console.error('停止录音失败:', error);
  }
};</code></pre>
<hr />
<h3><strong>4. 获取百度语音识别 Token</strong></h3>
<p>百度语音识别 API 使用 OAuth 2.0 授权机制，因此需要先获取 <code>access_token</code>。</p>
<pre><code class="language-javascript">import axios from 'axios';

const getAccessToken = async (apiKey, secretKey) =&gt; {
  const url = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&amp;client_id=${apiKey}&amp;client_secret=${secretKey}`;
  try {
    const response = await axios.get(url);
    return response.data.access_token;
  } catch (error) {
    console.error('获取 access_token 失败:', error);
    throw error;
  }
};</code></pre>
<hr />
<h3><strong>5. 调用百度语音识别 API</strong></h3>
<p>将录制的音频文件上传到百度语音识别 API，并解析返回的结果。</p>
<h4><strong>发送音频文件</strong></h4>
<p>百度语音识别支持两种方式：</p>
<ul>
<li><strong>直接上传音频数据</strong>（适合短语音）。</li>
<li><strong>通过 WebSocket 流式上传</strong>（适合长语音）。</li>
</ul>
<p>以下是一个简单的示例，使用直接上传的方式：</p>
<pre><code class="language-javascript">const recognizeSpeech = async (filePath, accessToken) =&gt; {
  const url = `https://vop.baidu.com/server_api?dev_pid=1537&amp;cuid=YOUR_DEVICE_ID&amp;token=${accessToken}`;

  // 读取音频文件内容
  const audioData = await RNFS.readFile(filePath, 'base64');

  try {
    const response = await axios.post(url, {
      format: 'pcm',
      rate: 16000,
      channel: 1,
      token: accessToken,
      speech: audioData, // Base64 编码的音频数据
      len: audioData.length,
    });

    if (response.data.err_no === 0) {
      console.log('识别结果:', response.data.result[0]);
      return response.data.result[0]; // 返回识别的文字
    } else {
      console.error('识别失败:', response.data.err_msg);
      throw new Error(response.data.err_msg);
    }
  } catch (error) {
    console.error('语音识别请求失败:', error);
    throw error;
  }
};</code></pre>
<hr />
<h3><strong>6. 完整代码示例</strong></h3>
<p>以下是一个完整的示例，展示如何录制音频并通过百度语音识别 API 转换为文字：</p>
<pre><code class="language-javascript">import React, { useState } from 'react';
import { Button, Text, View } from 'react-native';
import SoundRecorder from 'react-native-sound-recorder';
import axios from 'axios';

const App = () =&gt; {
  const [recognizedText, setRecognizedText] = useState('');
  const apiKey = 'YOUR_API_KEY'; // 替换为你的 API Key
  const secretKey = 'YOUR_SECRET_KEY'; // 替换为你的 Secret Key

  const startRecording = async () =&gt; {
    try {
      await SoundRecorder.start(SoundRecorder.PATH_CACHE + '/audio.pcm', {
        format: 'pcm',
        encoder: 'pcm',
        channels: 1,
        sampleRate: 16000,
      });
      console.log('开始录音');
    } catch (error) {
      console.error('录音失败:', error);
    }
  };

  const stopRecordingAndRecognize = async () =&gt; {
    try {
      const filePath = await SoundRecorder.stop();
      console.log('录音结束，文件路径:', filePath);

      // 获取 access_token
      const accessToken = await getAccessToken(apiKey, secretKey);

      // 调用百度语音识别 API
      const result = await recognizeSpeech(filePath, accessToken);
      setRecognizedText(result);
    } catch (error) {
      console.error('语音识别失败:', error);
    }
  };

  const getAccessToken = async (apiKey, secretKey) =&gt; {
    const url = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&amp;client_id=${apiKey}&amp;client_secret=${secretKey}`;
    const response = await axios.get(url);
    return response.data.access_token;
  };

  const recognizeSpeech = async (filePath, accessToken) =&gt; {
    const url = `https://vop.baidu.com/server_api?dev_pid=1537&amp;cuid=YOUR_DEVICE_ID&amp;token=${accessToken}`;
    const audioData = await RNFS.readFile(filePath, 'base64');

    const response = await axios.post(url, {
      format: 'pcm',
      rate: 16000,
      channel: 1,
      token: accessToken,
      speech: audioData,
      len: audioData.length,
    });

    if (response.data.err_no === 0) {
      return response.data.result[0];
    } else {
      throw new Error(response.data.err_msg);
    }
  };

  return (
    &lt;View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}&gt;
      &lt;Text style={{ fontSize: 20, marginBottom: 20 }}&gt;识别结果: {recognizedText}&lt;/Text&gt;
      &lt;Button title="开始录音" onPress={startRecording} /&gt;
      &lt;Button title="停止录音并识别" onPress={stopRecordingAndRecognize} /&gt;
    &lt;/View&gt;
  );
};

export default App;</code></pre>
<hr />
<h3><strong>7. 注意事项</strong></h3>
<ol>
<li><strong>音频格式</strong>：百度语音识别要求音频为单声道、16kHz 采样率的 PCM 格式。</li>
<li><strong>网络权限</strong>：确保在 Android 和 iOS 上添加网络权限。
<ul>
<li>Android: 在 <code>AndroidManifest.xml</code> 中添加 <code>&lt;uses-permission android:name="android.permission.INTERNET" /&gt;</code></li>
<li>iOS: 默认支持网络权限。</li>
</ul></li>
<li><strong>错误处理</strong>：百度语音识别 API 返回的错误码需要仔细处理，例如 <code>3300</code> 表示参数错误，<code>3301</code> 表示音频质量问题等。</li>
</ol>
<hr />
<h3><strong>8. 总结</strong></h3>
<p>通过上述步骤，你可以轻松地在 React Native 应用中集成百度语音识别功能。此方法适用于大多数场景，尤其是需要高精度识别的场合。如果遇到问题或需要进一步优化，请随时提问！</p>]]></description>
    <pubDate>Fri, 30 May 2025 01:47:18 +0800</pubDate>
    <dc:creator>Mr 焦</dc:creator>
    <guid>https://www.mtsws.cn/post-9.html</guid>
</item>
<item>
    <title>react-native-reanimated用法清单</title>
    <link>https://www.mtsws.cn/post-8.html</link>
    <description><![CDATA[<p>以下是 <code>react-native-reanimated</code> 的用法清单和核心文档说明，涵盖主要功能、API 和使用示例：</p>
<hr />
<h3><strong>一、核心概念</strong></h3>
<ol>
<li><strong>工作线程</strong>：动画在 UI 线程执行（非 JS 线程），避免卡顿</li>
<li><strong>共享值（Shared Values）</strong>：动画的驱动数据（代替 <code>Animated.Value</code>）</li>
<li><strong>动画修饰器</strong>：定义动画行为（如 <code>withTiming</code>, <code>withSpring</code>）</li>
<li><strong>手势集成</strong>：与 <code>react-native-gesture-handler</code> 深度结合</li>
</ol>
<hr />
<h3><strong>二、安装</strong></h3>
<pre><code class="language-bash"># 安装依赖
npm install react-native-reanimated

# 或使用 Expo
expo install react-native-reanimated</code></pre>
<p>在 <code>babel.config.js</code> 中添加插件：</p>
<pre><code class="language-js">plugins: ['react-native-reanimated/plugin']</code></pre>
<hr />
<h3><strong>三、核心 API 清单</strong></h3>
<h4><strong>1. 动画值</strong></h4>
<table>
<thead>
<tr>
<th>API</th>
<th>描述</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>useSharedValue</code></td>
<td>创建动画驱动值</td>
<td><code>const width = useSharedValue(100)</code></td>
</tr>
<tr>
<td><code>useDerivedValue</code></td>
<td>派生值（依赖其他共享值）</td>
<td><code>const ratio = useDerivedValue(() =&gt; width.value / 2)</code></td>
</tr>
<tr>
<td><code>useAnimatedReaction</code></td>
<td>响应共享值变化</td>
<td><code>useAnimatedReaction(() =&gt; count.value, (val) =&gt; { /* ... */ })</code></td>
</tr>
</tbody>
</table>
<h4><strong>2. 动画样式</strong></h4>
<table>
<thead>
<tr>
<th>API</th>
<th>描述</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>useAnimatedStyle</code></td>
<td>生成动态样式</td>
<td><a href="#示例1-基础动画">示例</a></td>
</tr>
<tr>
<td><code>useAnimatedProps</code></td>
<td>生成动态属性（用于非样式组件）</td>
<td><a href="#示例2-动画属性">示例</a></td>
</tr>
</tbody>
</table>
<h4><strong>3. 动画函数</strong></h4>
<table>
<thead>
<tr>
<th>函数</th>
<th>描述</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>withTiming</code></td>
<td>平滑过渡动画</td>
<td>大小、透明度变化</td>
</tr>
<tr>
<td><code>withSpring</code></td>
<td>弹簧物理动画</td>
<td>弹性效果</td>
</tr>
<tr>
<td><code>withDelay</code></td>
<td>延迟执行</td>
<td>序列动画</td>
</tr>
<tr>
<td><code>withRepeat</code></td>
<td>循环动画</td>
<td>呼吸效果</td>
</tr>
<tr>
<td><code>withSequence</code></td>
<td>顺序执行多个动画</td>
<td>复杂动画序列</td>
</tr>
<tr>
<td><code>withDecay</code></td>
<td>衰减动画（如滑动惯性）</td>
<td>列表滑动</td>
</tr>
</tbody>
</table>
<h4><strong>4. 手势处理</strong></h4>
<table>
<thead>
<tr>
<th>API</th>
<th>描述</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GestureDetector</code></td>
<td>手势容器组件</td>
<td><a href="#示例3-手势交互">示例</a></td>
</tr>
<tr>
<td><code>useAnimatedGestureHandler</code></td>
<td>手势事件处理（旧版）</td>
<td>兼容旧项目</td>
</tr>
</tbody>
</table>
<h4><strong>5. 布局动画</strong></h4>
<table>
<thead>
<tr>
<th>API</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Layout</code></td>
<td>自动布局动画（Reanimated v3+）</td>
</tr>
<tr>
<td><code>Entering/Exiting</code></td>
<td>进入/退出动画</td>
</tr>
<tr>
<td><code>FadeIn/FadeOut</code></td>
<td>内置淡入淡出效果</td>
</tr>
</tbody>
</table>
<hr />
<h3><strong>四、关键用法示例</strong></h3>
<h4><strong>示例1: 基础动画（缩放+透明度）</strong></h4>
<pre><code class="language-jsx">import Animated, { 
  useSharedValue, 
  useAnimatedStyle, 
  withTiming 
} from 'react-native-reanimated';

const ScaleBox = () =&gt; {
  const scale = useSharedValue(1);
  const opacity = useSharedValue(1);

  const animatedStyle = useAnimatedStyle(() =&gt; ({
    transform: [{ scale: scale.value }],
    opacity: opacity.value
  }));

  const onPress = () =&gt; {
    scale.value = withTiming(0.8, { duration: 300 });
    opacity.value = withTiming(0.5, { duration: 300 });
  };

  return (
    &lt;Animated.View style={[styles.box, animatedStyle]}&gt;
      &lt;Button title="Animate" onPress={onPress} /&gt;
    &lt;/Animated.View&gt;
  );
};</code></pre>
<h4><strong>示例2: 手势拖拽</strong></h4>
<pre><code class="language-jsx">import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';

const Draggable = () =&gt; {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);

  const panGesture = Gesture.Pan()
    .onChange((e) =&gt; {
      translateX.value += e.changeX;
      translateY.value += e.changeY;
    });

  const animatedStyle = useAnimatedStyle(() =&gt; ({
    transform: [
      { translateX: translateX.value },
      { translateY: translateY.value }
    ]
  }));

  return (
    &lt;GestureDetector gesture={panGesture}&gt;
      &lt;Animated.View style={[styles.draggable, animatedStyle]} /&gt;
    &lt;/GestureDetector&gt;
  );
};</code></pre>
<h4><strong>示例3: 关键帧动画</strong></h4>
<pre><code class="language-jsx">import { Keyframe } from 'react-native-reanimated';

const keyframe = new Keyframe({
  0: {
    transform: [{ rotate: '0deg' }],
    opacity: 0
  },
  30: {
    opacity: 0.3
  },
  100: {
    transform: [{ rotate: '360deg' }],
    opacity: 1
  }
});

// 使用
rotation.value = keyframe;</code></pre>
<hr />
<h3><strong>五、布局动画示例</strong></h3>
<pre><code class="language-jsx">import Animated, { Layout } from 'react-native-reanimated';

const ListItem = ({ text }) =&gt; (
  &lt;Animated.View 
    layout={Layout.springify()} // 自动添加布局动画
    style={styles.item}
  &gt;
    &lt;Text&gt;{text}&lt;/Text&gt;
  &lt;/Animated.View&gt;
);

// 进入/退出动画
&lt;Animated.View 
  entering={FadeIn.duration(500)} 
  exiting={FadeOut}
/&gt;</code></pre>
<hr />
<h3><strong>六、性能优化技巧</strong></h3>
<ol>
<li><strong>减少 JS 线程通信</strong>：
<ul>
<li>使用 <code>runOnJS</code> 在 UI 线程执行简单逻辑
<pre><code class="language-js">useAnimatedReaction(
() =&gt; scroll.value,
(scroll) =&gt; runOnJS(updateBackend)(scroll)
);</code></pre></li>
</ul></li>
<li><strong>避免频繁更新</strong>：用 <code>useDerivedValue</code> 代替多个独立值</li>
<li><strong>简化样式</strong>：合并 <code>useAnimatedStyle</code> 调用</li>
</ol>
<hr />
<h3><strong>七、调试工具</strong></h3>
<pre><code class="language-js">import { enableLayoutAnimations } from 'react-native-reanimated';

// 开发时启用布局动画
enableLayoutAnimations(true);

// 日志调试
import { log } from 'react-native-reanimated';
useDerivedValue(() =&gt; log('Value:', sharedValue.value));</code></pre>
<hr />
<h3><strong>八、官方资源</strong></h3>
<ul>
<li><a href="https://docs.swmansion.com/react-native-reanimated/">文档</a></li>
<li><a href="https://docs.swmansion.com/react-native-reanimated/docs/api">API 参考</a></li>
<li><a href="https://github.com/software-mansion-labs/reanimated-2-playground">示例仓库</a></li>
</ul>
<blockquote>
<p><strong>注意</strong>：Reanimated 3.x+ 要求 React Native ≥ 0.64，推荐使用函数式组件和 Hooks 写法。</p>
</blockquote>]]></description>
    <pubDate>Thu, 29 May 2025 13:42:26 +0800</pubDate>
    <dc:creator>Mr 焦</dc:creator>
    <guid>https://www.mtsws.cn/post-8.html</guid>
</item>
<item>
    <title>js数据类型</title>
    <link>https://www.mtsws.cn/post-5.html</link>
    <description><![CDATA[<h1><a href="/js/basic-concept/data-types#%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B"></a>数据类型</h1>
<p>JavaScript 是一种 <strong>弱类型语言</strong> 或者说 <strong>动态语言</strong>。这意味着你不用提前声明变量的类型，在程序运行过程中，类型会被自动确定。</p>
<p>这也意味着你可以使用同个相同名称的变量保存不同类型的数据：</p>
<pre><code>&lt;p&gt;var foo = 42;&lt;/p&gt;&lt;p&gt;// foo is a Number now&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;var foo = 'bar';&lt;/p&gt;&lt;p&gt;// foo is a String now&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;var foo = true;&lt;/p&gt;&lt;p&gt;// foo is a Boolean now&lt;/p&gt;</code></pre>
<p>💡 ECMAScript 标准定义了<strong>原始数据类型</strong>和<strong>引用数据类型</strong>，共七种内置类型：</p>
<p>原始数据类型（基本类型）：按值访问，可以操作保存在变量中实际的值。<strong>空值</strong>（null）<strong>未定义</strong>（undefined）<strong>布尔值</strong>（boolean）<strong>数字</strong>（number）<strong>字符串</strong>（string）<strong>符号</strong>（symbol）<br />
引用类型（复杂数据类型）：引用类型的值是保存在内存中的对象。<strong>对象</strong>（Object）布尔对象（Boolean）数字对象（Number）字符串对象（String）函数对象（Function）数组对象（Array）日期对象（Date）正则对象（RegExp）错误对象（Error）</p>
<p>⚠️ <strong>注意</strong>： 与其他语言不同的是，JavaScript 不允许直接访问内存中的位置，也就是说不能直接操作对象的内存空间。在操作对象时，实际上是在操作对象的引用而不是实际的对象。所以引用类型的值是按引用访问的。</p>
<h2><a href="/js/basic-concept/data-types#%E5%8E%9F%E5%A7%8B%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B"></a>原始数据类型</h2>
<h3><a href="/js/basic-concept/data-types#%E7%A9%BA%E5%80%BC"></a>空值</h3>
<p>空值 <code>null</code> 是一个字面量，它不像 <code>undefined</code> 是全局对象的一个属性。</p>
<p><code>null</code> 是表示缺少的标识，指示变量未指向任何对象。把 <code>null</code> 作为尚未创建的对象，也许更好理解。</p>
<p>🌰 <strong>代码示例</strong>：</p>
<p><code>foo</code> 不存在，它从来没有被定义过或者是初始化过。</p>
<pre><code>&lt;p&gt;foo;&lt;/p&gt;&lt;p&gt;&gt; "ReferenceError: foo is not defined"&lt;/p&gt;</code></pre>
<p><code>foo</code> 现在已经是知存在的，但是它没有类型或者是值。</p>
<pre><code>&lt;p&gt;var foo = null;&lt;/p&gt;&lt;p&gt;foo;&lt;/p&gt;&lt;p&gt;&gt; null&lt;/p&gt;</code></pre>
<h3><a href="/js/basic-concept/data-types#%E6%9C%AA%E5%AE%9A%E4%B9%89%E5%80%BC"></a>未定义值</h3>
<p>未定义值 <code>undefined</code> 是全局对象的一个属性。也就是说，它是全局作用域的一个变量。<code>undefined</code> 的最初值就是原始数据类型 <code>undefined</code>。</p>
<pre><code>&lt;p&gt;var foo;&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;console.log(foo);&lt;/p&gt;&lt;p&gt;// undefined&lt;/p&gt;</code></pre>
<h3><a href="/js/basic-concept/data-types#%E5%B8%83%E5%B0%94%E5%80%BC"></a>布尔值</h3>
<p>布尔类型表示一个逻辑实体，可以有两个值：<code>true</code> 和 <code>false</code></p>
<h3><a href="/js/basic-concept/data-types#%E6%95%B0%E5%AD%97"></a>数字</h3>
<h4><a href="/js/basic-concept/data-types#%E8%BF%9B%E5%88%B6%E6%95%B0"></a>进制数</h4>
<p>十进制：JavaScript 中默认的进制数八进制：第一位必须是 0，然后是 0-7 的数字组成十六进制：前两位必须是 <code>0x</code>，然后是 0-9 及 A-F（字母不区分大小写）</p>
<pre><code>&lt;p&gt;// 十进制&lt;/p&gt;&lt;p&gt;var num1 = 10;&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;// 八进制的56&lt;/p&gt;&lt;p&gt;var num2 = 070;&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;// 十进制，因为有数字超过了7，这里是79&lt;/p&gt;&lt;p&gt;var num3 = 079;&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;// 十六进制的31&lt;/p&gt;&lt;p&gt;var num4 = 0x1f;&lt;/p&gt;</code></pre>
<p>⚠️ <strong>注意</strong>： 八进制在严格模式下 <code>"use strict"</code> 是无效的，会导致 JavaScript 报错，避免使用。</p>
<h4><a href="/js/basic-concept/data-types#%E6%B5%AE%E7%82%B9%E6%95%B0"></a>浮点数</h4>
<pre><code>&lt;p&gt;var num = 0.1 + 0.2;&lt;/p&gt;&lt;p&gt;var sum = '2.3' * 100;&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;console.log(num);&lt;/p&gt;&lt;p&gt;// 0.30000000000000000004&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;console.log(sum);&lt;/p&gt;&lt;p&gt;// 229.99999999999997&lt;/p&gt;</code></pre>
<p>上面例子表达的就是 JavaScript 的浮点型数据在计算时容易丢失精度，这一点并不仅在 JavaScript 存在，建议处理这方面问题使用专用的数字处理类，比如 Java 里的 BigDecima 类来处理。</p>
<h4><a href="/js/basic-concept/data-types#%E6%95%B0%E5%AD%97%E7%9A%84%E8%8C%83%E5%9B%B4"></a>数字的范围</h4>
<p>JavaScript 中数值的范围是有效位数的，基本上够我们使用，我们仅需要知道以下几个知识点：</p>
<p><code>Number.MIN_VALUE</code> 或 <code>Number.NEGATIVE_INFINITY</code>：表示 JavaScript 中的最小值<code>Number.MAX_VALUE</code> 或 <code>Number.POSITIVE_INFINITY</code>：表示 JavaScript 中的最大值<code>Infinity</code>：表示无穷大<code>-Infinity</code>：表示无穷小</p>
<h4><a href="/js/basic-concept/data-types#nan"></a>NaN</h4>
<p><code>NaN</code> （Not a number）的含义是本该返回数值的操作未返回数值，返回了 <code>NaN</code> 就不会抛出异常影响语句流畅性。</p>
<p><code>NaN</code> 属性的初始值就是 <code>NaN</code>，和 <code>Number.NaN</code> 的值一样。</p>
<p>在现代浏览器中（ES5 环境）， <code>NaN</code> 属性是一个不可配置（non-configurable）、不可写（non-writable）的属性。但在 ES3 中，这个属性的值是可以被更改的，但是也应该避免覆盖。</p>
<p>编码中很少直接使用到 <code>NaN</code>。通常都是在<strong>计算失败</strong>时，作为 <code>Math</code> 的某个方法的返回值出现的（例如：<code>Math.sqrt(-1)</code>）或者尝试将一个字符串解析成数字但失败了的时候（例如：<code>parseInt("blabla")</code>）。</p>
<h3><a href="/js/basic-concept/data-types#%E5%AD%97%E7%AC%A6%E4%B8%B2"></a>字符串</h3>
<p>JavaScript 的字符串类型用于表示文本数据。它是一组 16 位的无符号整数值的元素。在字符串中的每个元素占据了字符串的位置。第一个元素的索引为 0，下一个是索引 1，依此类推。字符串的长度是它的元素的数量。</p>
<pre><code>&lt;p&gt;'foo';&lt;/p&gt;&lt;p&gt;'bar';&lt;/p&gt;&lt;p&gt;'1234';&lt;/p&gt;&lt;p&gt;'one line \n another line';&lt;/p&gt;&lt;p&gt;"John's cat";&lt;/p&gt;</code></pre>
<h3><a href="/js/basic-concept/data-types#%E7%AC%A6%E5%8F%B7"></a>符号</h3>
<p>符号（Symbols）是 ECMAScript 第 6 版新定义的。该类型的性质在于这个类型的值可以用来创建匿名的对象属性。该数据类型通常被用作一个对象属性的键值，当这个属性是用于类或对象类型的内部使用的时候。</p>
<pre><code>&lt;p&gt;var myPrivateMethod = Symbol();&lt;/p&gt;&lt;p&gt;
&lt;/p&gt;&lt;p&gt;this[myPrivateMethod] = function () {&lt;/p&gt;&lt;p&gt;  // ...&lt;/p&gt;&lt;p&gt;};&lt;/p&gt;</code></pre>
<h2><a href="/js/basic-concept/data-types#%E5%BC%95%E7%94%A8%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B"></a>引用数据类型</h2>
<p>引用类型通常叫做类（Class），也就是说，遇到引用值，所处理的就是对象。</p>
<p>在 ECMA-262 标准中根本没有出现 <strong>类</strong> 这个词，而是定义了 <strong>对象定义</strong>，逻辑上等价于其他程序设计语言中的类。</p>
<p>对象是由 <code>new</code> 运算符加上要实例化的对象的名字创建的。</p>
<p>例如，下面的代码创建 Object 对象的实例：</p>
<pre><code>&lt;p&gt;var o = new Object();&lt;/p&gt;</code></pre>
<p>这种语法与 Java 语言的相似，不过当有不止一个参数时，ECMAScript 要求使用括号。</p>
<p>如果没有参数，如以下代码所示，括号可以省略：</p>
<pre><code class="language-js">&lt;p&gt;var o = new Object();&lt;/p&gt;</code></pre>
<p>尽管括号不是必需的，但是为了避免混乱，最好使用括号。</p>
<hr />
<p><strong>参考资料</strong>：</p>
<p><a href="https://developer.mozilla.org/zh-CN/docs/Glossary/Symbol">📖 Symbol 术语表</a><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects">📖 Global Objects</a></p>]]></description>
    <pubDate>Tue, 27 May 2025 13:30:54 +0800</pubDate>
    <dc:creator>Mr 焦</dc:creator>
    <guid>https://www.mtsws.cn/post-5.html</guid>
</item>
<item>
    <title>js 基础语法</title>
    <link>https://www.mtsws.cn/post-4.html</link>
    <description><![CDATA[<p>ECMAScript 源码文本会被从左到右扫描，并被转换为一系列的输入元素，包括标识符、控制符、行终止符、注释和空白符。</p>
<p>同样地，ECMAScript 也定义了一些关键字、字面量以及行尾分号补全的规则。</p>
<h2><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E5%AD%97%E7%AC%A6%E9%9B%86"></a>字符集</h2>
<p>JavaScript 程序使用 Unicode 字符集编写。Unicode 是 ASCII 和 Latin-1 的超集，并支持地球上几乎所有在使用的语言。ECMAScript3 要求 JavaScript 的实现必须支持 Unicode2.1 及后续版本，ECMAScript5 则要求支持 Unicode3 及后续版本。</p>
<h3><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E5%8C%BA%E5%88%86%E5%A4%A7%E5%B0%8F%E5%86%99"></a>区分大小写</h3>
<p>JavaScript 是区分大小写的语言，也就是说，关键字、变量、函数名和所有的标识符（Identifier）都必须采取一致的大小写的形式。但是需要注意的是，HTML 和 CSS 并不区分大小写（尽管 XHTML 区分大小写），也就是说如果我们在用 JavaScript 控制 HTML 属性的时候对 HTML 来说 <code>id</code> 和 <code>ID</code> 没区别，但是 JavaScript 有区别。</p>
<p>🌰 <strong>代码示例</strong>：</p>
<p><code>abc</code>、<code>Abc</code>、<code>aBc</code>、<code>abC</code>、<code>ABC</code> 是五个不同的变量名。</p>
<pre><code class="language-javascript">var abc = 1;

var Abc = 2;

var aBc = 3;

var abC = 4;

var ABC = 5;

console.log(abc, Abc, aBc, abC, ABC); // 1 2 3 4 5</code></pre>
<h3><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E7%A9%BA%E6%A0%BC%E6%8D%A2%E8%A1%8C%E7%AC%A6%E5%92%8C%E6%A0%BC%E5%BC%8F%E6%8E%A7%E5%88%B6%E7%AC%A6"></a>空格、换行符和格式控制符</h3>
<p>JavaScript 会忽略程序中 <strong>标识</strong>（Token）之间的空格。多数情况下，JavaScript 同样会忽略换行符。由于可以在代码中随意使用空格和换行，因此可以采用整齐、一致的缩进来形成统一的编码风格，从而提高代码的可读性。</p>
<h4><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E7%A9%BA%E7%99%BD%E5%AD%97%E7%AC%A6"></a>空白字符</h4>
<p>空白字符 WhiteSpace</p>
<p>\u0009 水平制表符 <TAB></p>
<p>\u000B 垂直制表符 <VT></p>
<p>\u000C 换页符 <FF></p>
<p>\u0020 空格符 <SP></p>
<p>\u00A0 非中断空格符 <NBSP></p>
<p>\uFEFF 字符序标记</p>
<h4><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E8%A1%8C%E7%BB%88%E6%AD%A2%E7%AC%A6"></a>行终止符</h4>
<p>行终止符 LineTerminator</p>
<p>\u000A 换行符 <LF></p>
<p>\u000D 回车符 <CR></p>
<p>\u2028 行分隔符 <LS></p>
<p>\u2029 段落分割符 <PS></p>
<p>⚠️ <strong>注意</strong>：回车符加换行符在一起被解析成一个单行结束符</p>
<h3><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#unicode-%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97"></a>Unicode 转义序列</h3>
<p>在有些计算机硬件和软件里，无法显示或输入 Unicode 字符全集。为了兼容，JavaScript 定义了一种特殊序列，使用 6 个 ASCII 字符来代表任意 16 位 Unicode 内码。这些 Unicode 转义序列均以 <code>\u</code> 为前缀，其后跟随 4 个十六进制数（使用数数字以及大写或小写的字母 A~F 表示），可以用于 JavaScript 直接量、正则表达式和标识符中（关键字除外）。</p>
<h2><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E6%B3%A8%E9%87%8A"></a>注释</h2>
<p>JavaScript 不会执行注释。</p>
<p>我们可以添加注释来对 JavaScript 进行解释，或者提高代码的可读性。</p>
<h3><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E5%8D%95%E8%A1%8C%E6%B3%A8%E9%87%8A"></a>单行注释</h3>
<p>单行注释以两个斜杠开头</p>
<p>let a;</p>
<h3><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E5%A4%9A%E8%A1%8C%E6%B3%A8%E9%87%8A"></a>多行注释</h3>
<p>多行注释又叫块级注释，以 <code>/*</code> 开头，以 <code>*/</code> 结尾</p>
<p>/*</p>
<p>以下代码为</p>
<p>声明变量并</p>
<p>赋值</p>
<p>*/</p>
<p>let a;</p>
<p>a = 1;</p>
<p>块级注释 <code>/**/</code> 可以跨行书写，但不能嵌套，否则会报错。</p>
<p>// Error</p>
<p>/*</p>
<p>注释1</p>
<p>/*</p>
<p>注释1.1</p>
<p>*/</p>
<p>*/</p>
<p>块级注释 <code>/**/</code> 中的那些字符也可能出现在正则表达式字面量里，所以块级注释对于被注释的代码块来说是不安全的。</p>
<p>/*</p>
<p>var rm_a = /a*/.match(s);</p>
<p>*/</p>
<h3><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E9%98%BB%E6%AD%A2%E6%89%A7%E8%A1%8C"></a>阻止执行</h3>
<p>注释可用于阻止其中一条代码行的执行（可用于调试）：</p>
<p>// var a = 1;</p>
<p>var a = 2;</p>
<h3><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E8%A1%8C%E6%9C%AB%E6%B3%A8%E9%87%8A"></a>行末注释</h3>
<p>在下面的例子中，我们把注释放到代码行的结尾处：</p>
<p>var x = 5; // 声明 x 并把 5 赋值给它</p>
<p>var y = x + 2; // 声明 y 并把 x+2 赋值给它</p>
<h2><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E7%9B%B4%E6%8E%A5%E9%87%8F"></a>直接量</h2>
<p>JavaScript 数据直接量：<strong>直接量（Literals）</strong>，又名<strong>字面量</strong>，就是可以在程序中直接使用的数据。</p>
<p>主要有以下几种直接量：</p>
<p><strong>空直接量</strong></p>
<p>null;</p>
<p><strong>布尔直接量</strong></p>
<p>true;</p>
<p>false;</p>
<p><strong>数值直接量</strong></p>
<p>// 十进制</p>
<p>1234567890;</p>
<p>⚠️ <strong>注意</strong>：十进制数值直接量可以以 0 开头，但是如果 0 以后的最高位比 8 小，数值将会被认为是八进制而不会报错</p>
<p>// 二进制</p>
<p>0b10000000000000000000000000000000;</p>
<p>// 2147483648</p>
<p>二进制表示为开头是 0 后接大写或小写的 B（<code>0b</code>或者<code>0B</code>）。如果<code>0b</code>之后有除了 0 或 1 以外的数字，将会抛出错误。</p>
<p>// 八进制</p>
<p>0o755;</p>
<p>// 493</p>
<p>进制表示为开头是 0 后接大写或小写的 O（<code>0o</code> 或 <code>0O</code>）。如果有不在 <code>01234567</code> 中的数字，将会抛出错误。</p>
<p>// 十六进制</p>
<p>0xfffffffffffffffff;</p>
<p>// 295147905179352830000</p>
<p>十六进制表示为开头是 0 后接大写或小写的 X（<code>0x</code>或<code>0X</code>）。如果有不在 <code>0123456789ABCDEF</code> 中的数字，将会抛出错误。</p>
<p><strong>字符串直接量</strong></p>
<p>'foo';</p>
<p>'bar';</p>
<p>// 十六进制转义序列</p>
<p>'\xA9'; // &quot;©&quot;</p>
<p>// Unicode转义序列</p>
<p>'\u00A9'; // &quot;©&quot;</p>
<p><strong>对象直接量</strong></p>
<p>var o = { a: 'foo', b: 'bar', c: 42 };</p>
<p>// ES6中的简略表示方法</p>
<p>var a = 'foo',</p>
<p>b = 'bar',</p>
<p>c = 42;</p>
<p>var o = { a, b, c };</p>
<p>// 不需要这样</p>
<p>var o = { a: a, b: b, c: c };</p>
<p><strong>数组直接量</strong></p>
<p>[1954, 1974, 1990, 2014];</p>
<p><strong>正则表达式直接量</strong></p>
<p>一个空的正则表达式直接量，必须有一个空的非捕获分组，以避免被当成是行注释符号。</p>
<p>/ab+c/g</p>
<p>/(?:)/</p>
<p><strong>模板字符串直接量</strong></p>
<p>`string text``string text line 1</p>
<p>string text line 2<code>`string text ${expression} string text</code>;</p>
<h2><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E6%A0%87%E8%AF%86%E7%AC%A6"></a>标识符</h2>
<p><strong>标识符</strong>，就是指变量、函数、属性的名字，或者函数的参数。标识符可以是按照下列格式规则组合起来的一或多个字符。</p>
<ul>
<li>第一个字符必须是一个字母、下划线（<code>_</code>）、或一个美元符号（<code>$</code>）</li>
<li>其他字符可以是字母、下划线、美元符号或数字</li>
</ul>
<p>标识符中的字母也可以包含扩展的 ASCII 或 Unicode 字母字符，但我们不推荐这样做。</p>
<p>按照惯例，ECMAScript 标识符采用驼峰大小写格式，也就是第一个字母小写，剩下的每个单词的首字母大写。</p>
<p>const firstSecond = 123;</p>
<p>const myCar = 'Toyota';</p>
<p>const doSomethingImportant = function () {};</p>
<p>虽然没有谁强制要求必须采用这种格式，但为了与 ECMAScript 内置的函数和对象命名格式保持一致，可以将其当作一种最佳实践。</p>
<p>⚠️ <strong>注意</strong>：不能把关键字、保留字、<code>true</code>、<code>false</code> 和 <code>null</code> 用作标识符。</p>
<h2><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E4%BF%9D%E7%95%99%E5%AD%97"></a>关键字和保留字</h2>
<p>和其他任何编程语言一样，JavaScript 保留了一些标识符为自己所用。这些保留字不能用做普通的标识符。由于好多参考书的误导，貌似保留字和关键字是分开的，其实并不是，关键字只是保留字的一部分。</p>
<p>保留字包括<strong>关键字</strong>、<strong>未来保留字</strong>、<strong>空字面量</strong>和<strong>布尔值字面量</strong>。</p>
<h3><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E4%BF%9D%E7%95%99%E5%AD%97"></a>保留字</h3>
<ul>
<li>关键字 Keyword</li>
<li>未来保留字 FutureReservedWord</li>
<li>空字面量 NullLiteral</li>
<li>布尔值字面量 BooleanLiteral</li>
</ul>
<h3><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E5%85%B3%E9%94%AE%E5%AD%97"></a>关键字</h3>
<p>以下关键字 ES6 规范中已实现</p>
<p>break do instanceof typeof</p>
<p>case else new var</p>
<p>catch finally return void</p>
<p>continue for switch while</p>
<p>debugger function this with</p>
<p>default if throw delete</p>
<p>in try class extends</p>
<p>const export import</p>
<h3><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E6%9C%AA%E6%9D%A5%E4%BF%9D%E7%95%99%E5%AD%97"></a>未来保留字</h3>
<p>以上是 ECMAScript6 的保留字，但在 ECMAScript3 版本中的保留字并不一样，若希望代码能在基于 ECMAScript3 实现的解释器上运行的话，应该避免使用以下保留字作为标识符。</p>
<p>abstract boolean byte char</p>
<p>constdouble enum final float</p>
<p>goto implements int interfacelong</p>
<p>native package private protected</p>
<p>public short static super</p>
<p>throw transient volatile synchronized</p>
<p><strong>预定义变量和函数</strong></p>
<p>此外，JavaScript 预定义了很多全局变量和函数，应该避免把它们的名字用做标识符名。</p>
<p>String Number Boolean Array</p>
<p>Date Function Math Object</p>
<p>RegExp Error EvalError JSON</p>
<p>Infinity NaN isNaN isFinite</p>
<p>undefined arguments parseInt parseFloat</p>
<p>eval decodeURI encodeURI decodeURIComponent</p>
<p>encodeURIComponent RangeError ReferenceError</p>
<p>TypeError URIError SyntaxError</p>
<h2><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E5%88%86%E5%8F%B7"></a>分号</h2>
<p>JavaScript 使用分号 <code>;</code> 将语句分隔开，这对增强代码的可读性和整洁性是非常重要的。</p>
<p>有些地方可以省略分号，有些地方则不能省略分号。</p>
<ul>
<li><strong>两条语句用两行书写，第一个分号可以省略</strong></li>
</ul>
<p>a = 3;</p>
<p>b = 4;</p>
<ul>
<li><strong>两条语句用一行书写，第一个分号不能省略</strong></li>
</ul>
<p>a = 3;</p>
<p>b = 4;</p>
<p>但 JavaScript 并不是在所有换行处都填补分号，只有在缺少了分号无法正确解析代码时，JavaScript 才会填补分号。换句话说，如果当前语句和随后的非空格字符不能当成一个整体来解析的话，JavaScript 就在当前语句行结束处填补分号。</p>
<p>🌰 <strong>代码示例</strong>：</p>
<p>var a;</p>
<p>a = 3;</p>
<p>console.log(a);</p>
<p>JavaScript 将其解析为:</p>
<p>var a;</p>
<p>a = 3;</p>
<p>console.log(a);</p>
<h3><a href="https://www.jiaoxiaoyuan.cn/js/basic-concept#%E8%87%AA%E5%8A%A8%E5%88%86%E5%8F%B7%E8%A1%A5%E5%85%A8"></a>自动分号补全</h3>
<p>JavaScript 并不是在所有换行处都填补分号，只有在缺少了分号就无法正确解析代码时，JavaScript 才会填补分号。换句话说，如果当前语句和随后的非空格字符不能当成一个整体来解析的话，JavaScript 就在当前语句行结束处填补分号。</p>
<ul>
<li>当出现一个不允许的行终止符或 <code>}</code> 时，会在其之前插入一个分号。</li>
</ul>
<p>🌰 <strong>代码示例</strong>：</p>
<p>{ 1 2 } 3</p>
<p>// 将会被 ASI 转换为</p>
<p>{ 1 2 ;} 3;</p>
<p>当捕获到标识符输入流的结尾，并且无法将单个输入流转换为一个完整的程序时，将在结尾插入一个分号。</p>
<p>在下面这段中，由于在 <code>b</code> 和 <code>++</code> 之间出现了一个行终止符，所以 <code>++</code> 未被当成变量 <code>b</code> 的后置运算符。</p>
<p>🌰 <strong>代码示例</strong>：</p>
<p>a = b;</p>
<p>++c;</p>
<p>// 将被ASI转换为</p>
<p>a = b;</p>
<p>++c;</p>
<p>当语句中包含行终止符语法的时候（也就是语句后紧跟着换行），将会在行结尾插入一个分号。</p>
<p>带 <strong>这里没有行终止符</strong> 规则的语句有：</p>
<ul>
<li>后置运算符（<code>++</code> 和 <code>--</code>）</li>
<li><code>continue</code></li>
<li><code>break</code></li>
<li><code>return</code></li>
<li><code>yield</code>、<code>yield*</code></li>
<li><code>module</code></li>
</ul>
<p>return;</p>
<p>a + b;</p>
<p>// 将被 ASI 转换为</p>
<p>return;</p>
<p>a + b;</p>
<p>x;</p>
<p>++y;</p>
<p>// 解析为</p>
<p>x;</p>
<p>++y;</p>
<p>// 本意</p>
<p>x++;</p>
<p>y;</p>
<p>虽然分号不是必须的，但最好不要省略它，因为加上分号可以避免很多错误，代码行结尾处没有分号会导致压缩错误。加上分号也会在某些情况下增进代码的性能，因为这样解析器就不必再花时间推测应该在哪里插入分号了。</p>]]></description>
    <pubDate>Tue, 27 May 2025 13:04:25 +0800</pubDate>
    <dc:creator>Mr 焦</dc:creator>
    <guid>https://www.mtsws.cn/post-4.html</guid>
</item></channel>
</rss>