LeanCloud—JavaScript指南

初步理解AV

AV.Object

AV.Object 是云服务对复杂对象的封装,每个 AV.Object 包含若干与 JSON 格式兼容的属性值对(也称键值对,key-value pairs)。这个数据是无模式化的(schema free),意味着你不需要提前标注每个 AV.Object 上有哪些 key,你只需要随意设置键值对就可以,云端会保存它

比如说,一个保存着单个 Todo 的 AV.Object 可能包含如下数据:

title:      "给小林发邮件确认会议时间",
isComplete: false,
priority:   2,
tags:       ["工作", "销售"]

 数据类型

AV.Object 支持的数据类型包括 StringNumberBooleanObjectArrayDate 等等。你可以通过嵌套的方式在 Object 或 Array 里面存储更加结构化的数据

AV.Object 还支持两种特殊的数据类型 Pointer 和 File,可以分别用来存储指向其他 AV.Object 的指针以及二进制数据

// 基本类型
const bool = true;
const number = 2023;
const string = `${number} 流行音乐榜单`;
const date = new Date();
const array = [string, number];
const object = {
  number: number,
  string: string,
};

// 构建对象
const TestObject = AV.Object.extend("TestObject");//新建一个class
const testObject = new TestObject();//创建一个实例对象
testObject.set("testNumber", number);//设置对应字段和值,以下同理
testObject.set("testString", string);
testObject.set("testDate", date);
testObject.set("testArray", array);
testObject.set("testObject", object);
testObject.save();//保存到云端

 我们不推荐在 AV.Object 里面存储图片、文档等大型二进制数据。每个 AV.Object 的大小不应超过 128 KB。如需存储大型文件,可创建 AV.File 实例并将其关联到 AV.Object 的某个属性上。具体文章下面会提到

注意:时间类型在云端将会以 UTC 时间格式存储,但是客户端在读取之后会转化成本地时间。

云服务控制台 > 数据存储 > 结构化数据 中展示的日期数据也会依据操作系统的时区进行转换。一个例外是当你通过 REST API 获得数据时,这些数据将以 UTC 呈现。你可以手动对它们进行转换。

构建对象

方式一:

在构建对象时,为了使云端知道对象属于哪个 class,需要将 class 的名字作为参数传入。你可以将云服务里面的 class 比作关系型数据库里面的表。一个 class 的名字必须以字母开头,且只能包含数字、字母和下划线,class类名的命名最好以大驼峰式命名例如:OfferJob

// 为 AV.Object 创建子类,类名为Todo
const Todo = AV.Object.extend("Todo");

// 为该类创建一个新实例
const todo = new Todo();

// 你还可以直接使用 AV.Object 的构造器
const todo = new AV.Object("Todo");

方式二:

如果你使用的是 ES6,还可以通过 extends 关键字来创建 AV.Object 的子类,然而 SDK 无法自动识别你创建的子类。你需要通过这种方式手动注册一下:

class Todo extends AV.Object {
  // 自定义属性和方法
}

// 注册子类
AV.Object.register(Todo);

保存对象

className.save()

// 声明 class
const TableHandler = AV.Object.extend("ZtlOfferJob");

// 构建对象
const new_item = new TableHandler();

// 为属性赋值
new_item .set("job_name", job_name);
new_item .set("job_sex", 2job_sex;

// 将对象保存到云端
new_item.save().then(
  (it) => {
    // 成功保存之后,执行其他逻辑
    console.log(`保存成功。objectId:${it.id}`);
  },
  (error) => {
    // 异常处理
  }
);
   

如果class不存在,运行代码则自动创建

以下是对象的内置属性,将在对象保存时自动创建

内置属性类型描述
objectIdString该对象唯一的 ID 标识。
ACLAV.ACL该对象的权限控制,实际上是一个 JSON 对象,控制台做了展现优化。
createdAtDate该对象被创建的时间。
updatedAtDate该对象最后一次被修改的时间。

对于已经存在在云端的AV.Object,可以通过它的ObjectId来获取

//查询类名为OfferJob的数据表
const query = new AV.Query("OfferJob");
//通过一个ObjectId来查询该条数据
query.get("582570f38ac247004f39c24b").then((it) => {
  // it就是 objectId 为 582570f38ac247004f39c24b 的 OfferJob实例
  const title = it.get("title");
  const priority = it.get("priority");
  // 获取内置属性
  const objectId = it.id;
  const updatedAt = it.updatedAt;
  const createdAt = it.createdAt;
});

如果你试图获取一个不存在的属性,SDK 不会报错,而是会返回 undefined。所以要注意属性值要设置,我一般是给它置空或者先定义

如果需要一次性获取返回对象的所有属性(比如进行数据绑定)而非显式地调用 get,可以利用 AV.Object 实例的 toJSON 方法:

const query = new AV.Query("Todo");
query.get("582570f38ac247004f39c24b").then((todo) => {
  console.log(todo.toJSON());
});

同步对象

当云端数据发生更改时,你可以调用 fetch 方法来刷新对象,使之与云端数据同步:

const todo = AV.Object.createWithoutData("Todo", "582570f38ac247004f39c24b");
todo.fetch().then((todo) => {
  // todo 已刷新
});

刷新操作会强行使用云端的属性值覆盖本地的属性。因此如果本地有属性修改,fetch 操作会丢弃这些修改。为避免这种情况,你可以在fetch括号里面加keys,里面再加你想让他刷新的属性,这样只有指定的属性会被刷新(包括内置属性 objectIdcreatedAt 和 updatedAt),其他属性不受影响

const todo = AV.Object.createWithoutData("Todo", "582570f38ac247004f39c24b");
todo
  .fetch({
    keys: "priority, location",
  })
  .then((todo) => {
    // 只有 priority 和 location 会被获取和刷新
  });

更新对象

要更新一个对象,只需指定需要更新的属性名和属性值,然后调用 save 方法。例如:

const todo = AV.Object.createWithoutData("Todo", "582570f38ac247004f39c24b");
todo.set("content", "这周周会改到周三下午三点。");
todo.save();

如果想要查看哪些属性有尚未保存的修改,可以调用 dirtyKeys 方法

todo.dirtyKeys(); // ['content']

如果希望撤销尚未保存的修改,可以调用 revert 方法。直接调用 revert() 将撤销所有尚未保存的修改。还可以额外传入 keys 数组作为参数,指定需要撤销的属性

todo.revert(["content"]);

删除对象

从云端删除一个 Todo 对象,分为软删除和硬删除

软删除:className.set('enable',false),数据还在,只是让它不显示而已

data_handler: async (params) => {
        const { object_id, AV } = params;
        const query = new AV.Query('ZtlOfferJob');
        const this_obj = await query.get(object_id);
        if (!this_obj) {
            throw new Error('没有检索到业务对象');
        }
        this_obj.set('enable', false);
        return await this_obj.save();
    }

硬删除:object.destroy(),直接删除数据

const todo = AV.Object.createWithoutData("Todo", "582570f38ac247004f39c24b");
todo.destroy();

如果只需删除对象的一个属性,可以用 unset

const todo = AV.Object.createWithoutData("Todo", "582570f38ac247004f39c24b");

// priority 属性会被删除
todo.unset("priority");

// 保存对象
todo.save();

批量操作

1.saveAll

2.destroyAll

3.fetchAll

示例:下面的代码将所有 Todo 的 isComplete 设为 true

const query = new AV.Query("Todo");
query.find().then((todos) => {
  // 获取需要更新的 todo
  todos.forEach((todo) => {
    // 更新属性值
    todo.set("isComplete", true);
  });
  // 批量更新
  AV.Object.saveAll(todos);
});

 序列化和反序列化

在实际的开发中,把 AV.Object 当作参数传递的时候,会涉及到复杂对象的拷贝的问题,因此 AV.Object 也提供了序列化和反序列化的方法

序列化:

const todo = new AV.Object("Todo"); // 构建对象
todo.set("title", "马拉松报名"); // 设置名称
todo.set("priority", 2); // 设置优先级
todo.set("owner", AV.User.current()); // 这里就是一个 Pointer 类型,指向当前登录的用户

// 将 AV.Object 对象序列化成 JSON 对象
const json = todo.toFullJSON();
// 将 JSON 对象序列化为字符串
const serializedString = JSON.stringify(json);

AV.Object 还提供了另一个方法 toJSON()。它们的区别是 toJSON() 得到的对象仅包含对象的 payload,一般用于展示,而 toFullJSON() 得到的对象包含了元数据,一般用于传输。在使用时请注意区分。

反序列化:

// 将字符串反序列化为 JSON 对象
const json = JSON.parse(serializedString);
// 将 JSON 对象反序列化成 AV.Object 对象
const todo = AV.parseJSON(json);

查询

基础查询

执行一次基础查询通常包括这些步骤:

构建 AV.Query

向其添加查询条件

执行查询并获取包含满足条件的对象的数组

 示例:

//查询所有lastname为“Smith”的对象组成的数组
const query = new AV.Query("Student");
query.equalTo("lastName", "Smith");
query.find().then((students) => {
  // students 是包含满足条件的 Student 对象的数组
});

查询条件

可以给 AV.Object 添加不同的条件来改变获取到的结果

//查询所有firstName不为Jack的对象
query.notEqualTo("firstName", "Jack");

对于能够排序的属性(比如数字、字符串),可以进行比较查询

lessThan

lessThanOrEqualTo

greaterThan

greaterThanOrEqualTo

// 限制 age < 18
query.lessThan("age", 18);

// 限制 age <= 18
query.lessThanOrEqualTo("age", 18);

// 限制 age > 18
query.greaterThan("age", 18);

// 限制 age >= 18
query.greaterThanOrEqualTo("age", 18);

可以在同一个查询中设置多个条件,这样可以获取满足所有条件的结果,都满足条件了才能通过筛选

 可以通过指定 limit 限制返回结果的数量(默认为 100):

// 最多获取 10 条结果
query.limit(10);

由于性能原因,limit 最大只能设为 1000。即使将其设为大于 1000 的数,云端也只会返回 1,000 条结果。

如果只需要一条结果,可以直接用 first

//查询Todo这个类,取所有priority为2的数据
const query = new AV.Query("Todo");
query.equalTo("priority", 2);
//结果只能取第一个
query.first().then((todo) => {
  // todo 是第一个满足条件的 Todo 对象
});

 可以通过设置 skip 来跳过一定数量的结果:

// 跳过前 20 条结果
query.skip(20);

把 skip 和 limit 结合起来,就能实现翻页功能:

const query = new AV.Query("Todo");
query.equalTo("priority", 2);
query.limit(10);
query.skip(20);

 需要注意的是,skip 的值越高,查询所需的时间就越长。作为替代方案,可以通过设置 createdAt 或 updatedAt 的范围来实现更高效的翻页,因为它们都自带索引。 

 对于能够排序的属性,可以指定结果的排序规则:

// 按 createdAt 升序排列
query.ascending("createdAt");

// 按 createdAt 降序排列
query.descending("createdAt");

还可以为同一个查询添加多个排序规则:

query.addAscending("priority");
query.addDescending("createdAt");

下面的代码可用于查找包含或不包含某一属性的对象:

// 查找包含 'images' 的对象
query.exists("images");

// 查找不包含 'images' 的对象
query.doesNotExist("images");

 字符串查询

可以用 startsWith 来查找某一属性值以特定字符串开头的对象

const query = new AV.Query("Todo");
// 查找以lunch字符串开头的title
query.startsWith("title", "lunch");

可以用 contains 来查找某一属性值包含特定字符串的对象

const query = new AV.Query("Todo");
//这个挺重要,常用于模糊查询,查找含有lunch字符串的title,我一般不查字符串
//查由字符串组成的变量,因为用户输入的内容是不固定的
query.contains("title", "lunch");

注意 startsWith 和 contains 都是 区分大小写 的,所以上述查询会忽略 LunchLUNCH 等字符串。

如果想查找某一属性值不包含特定字符串的对象,可以使用 matches 进行基于正则表达式的查询(不推荐,影响性能): 

const query = new AV.Query("Todo");
// 'title' 不包含 'ticket'(不区分大小写)
const regExp = new RegExp("^((?!ticket).)*$", "i");
query.matches("title", regExp);

 下面的代码查找所有数组属性 tags 包含 工作 的对象:

//过滤出所有tags的属性值为数组并且里面包含‘工作’的数据
query.equalTo("tags", "工作");

下面的代码查询数组属性长度为 3(正好包含 3 个标签)的对象:

query.sizeEqualTo("tags", 3);

 下面的代码查找所有数组属性 tags 同时包含 工作销售 和 会议 的对象:

query.containsAll("tags", ["工作", "销售", "会议"]);

如需获取某一属性值包含一列值中任意一个值的对象,可以直接用 containedIn 而无需执行多次查询

const priorityOneOrTwo = new AV.Query("Todo");
//查找所有 priority 为 1 或 2 的 todo 对象
priorityOneOrTwo.containedIn("priority", [1, 2]);

反过来,还可以用 notContainedIn 来获取某一属性值不包含一列值中任何一个的对象。

const priorityOneOrTwo = new AV.Query("Todo");
priorityOneOrTwo.notContainedIn("priority", [1, 2]);

统计总数量

如果只需知道有多少对象匹配查询条件而无需获取对象本身,可使用 count 来代替 find

const query = new AV.Query("Todo");
query.equalTo("isComplete", true);
//会输出满足条件的数据的条数
query.count().then((count) => {
  console.log(`${count} 个 todo 已完成。`);
});

组合查询

组合查询就是把诸多查询条件用一定逻辑合并到一起(OR 或 AND)再交给云端去查询。

组合查询不支持在子查询中包含 GeoPoint 或其他非过滤性的限制(例如 nearwithinGeoBoxlimitskipascendingdescendinginclude)。

OR 查询 

OR 操作表示多个查询条件符合其中任意一个即可

const priorityQuery = new AV.Query("Todo");
priorityQuery.greaterThanOrEqualTo("priority", 3);

const isCompleteQuery = new AV.Query("Todo");
isCompleteQuery.equalTo("isComplete", true);
//两个查询条件满足一个即可
const query = AV.Query.or(priorityQuery, isCompleteQuery);

 AND 查询

使用 AND 查询的效果等同于往 AV.Query 添加多个条件

const startDateQuery = new AV.Query("Todo");
startDateQuery.greaterThanOrEqualTo(
  "createdAt",
  new Date("2023-05-20 00:00:00")
);

const endDateQuery = new AV.Query("Todo");
endDateQuery.lessThan("createdAt", new Date("2023-11-02 00:00:00"));
//两个条件都要满足才行
const query = AV.Query.and(startDateQuery, endDateQuery);

单独使用 AND 查询跟使用基础查询相比并没有什么不同,不过当查询条件中包含不止一个 OR 查询时,就必须使用 AND 查询

const createdAtQuery = new AV.Query("Todo");
createdAtQuery.greaterThanOrEqualTo("createdAt", new Date("2018-04-30"));
createdAtQuery.lessThan("createdAt", new Date("2018-05-01"));

const locationQuery = new AV.Query("Todo");
locationQuery.doesNotExist("location");

const priority2Query = new AV.Query("Todo");
priority2Query.equalTo("priority", 2);

const priority3Query = new AV.Query("Todo");
priority3Query.equalTo("priority", 3);
//当不止一个or的时候,必须用and,保证两边都为真才行
const priorityQuery = AV.Query.or(priority2Query, priority3Query);
const timeLocationQuery = AV.Query.or(locationQuery, createdAtQuery);
const query = AV.Query.and(priorityQuery, timeLocationQuery);

 查询性能优化

  1. 不等于和不包含查询(无法使用索引)
  2. 通配符在前面的字符串查询(无法使用索引)
  3. 有条件的 count(需要扫描所有数据)
  4. skip 跳过较多的行数(相当于需要先查出被跳过的那些行)
  5. 无索引的排序(另外除非复合索引同时覆盖了查询和排序,否则只有其中一个能使用索引)
  6. 无索引的查询(另外除非复合索引同时覆盖了所有条件,否则未覆盖到的条件无法使用索引,如果未覆盖的条件区分度较低将会扫描较多的数据)

其它的太难了,之后再写吧 !

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值