小天管理 发表于 2024年9月1日 发表于 2024年9月1日 前言 最近更新了我的系列文章,其中有一部分是关于 JavaScript 语言中“点表示法”的使用。 文章中有 5 个工具函数是纯JavaScript的,我觉得不仅仅是小程序项目用得上,其他前端 JS 项目也应该用得上。 我把文章中的这一部分单独整理出来,给需要写前端代码的朋友参考参考。 你可以直接在 Github 项目 中的前端 utils.js 文件中找到这 5 个工具函数。 下面是文章节选,完整的文章列表可以在 Github 项目 中查看。 点表示法a.b.c 用点表示法给对象赋值:putValue函数 想象一下,如果a是一个对象,你要给a.b.c.d赋值为 1 ,你会这样写? let a // 通过某种方式获得的对象 if (!a.b) { a.b = {} } if (!a.b.c) { a.b.c = {} } a.b.c.d = 1 拜托,大学生才这样写。为此,我们引入了putValue函数: utils.putValue(a, 'b.c.d', 1) putValue函数会自动创建a.b、a.b.c中间对象,它的声明如下: /** * 向对象中按照路径赋值,如果路径上的中间对象不存在,则自动创建。 * @param {Object} obj - 目标对象。 * @param {string} key - 属性路径,支持'a.b.c'形式。 * @param {*} value - 要设置的值。 * @param {Object} [options={}] - 可选参数。 * - {boolean} remove_undefined - 如果为 true 且 value 为 undefined ,则删除该属性。 * @throws {Error} 如果 obj 为 null 或 undefined ,或路径不合法(如中间非对象)则抛出异常。 */ putValue(obj, key, value, {remove_undefined = true} = {}){ // ... } 使用这个函数有两个地方要注意,一个是如果路径上的中间对象不是对象,会抛出异常。例如a.b=2,这里b不是对象,此时会抛出异常。 另一个是如果value是undefined,则会删除该属性。例如下面的代码: utils.putValue(a, 'b.c.d', undefined) console.log(a) // {b: {c: {}}},putValue 会自动创建中间对象,但不会自动删除空对象 但若你真想赋值为undefined,可设置remove_undefined参数为false。 读取对象属性值:pickValue函数 对应的,如果要获取a.b.c.d的值,可以使用pickValue函数: utils.putValue(a, 'b.c.d', 1) const value = utils.pickValue(a, 'b.c.d') console.log(value) // 1 当然你也可以直接使用javascript的原生语法: const value = a.b?.c?.d 这两种写法,当中间路径不存在时,均会返回undefined。 但a.b?.c?.d这种写法是硬编码,而在实际开发中路径可能是动态的。例如用户要修改一个配置项,这个配置可能是user_config.font.size也可能是user_config.page.color.background,如果使用硬编码的方式,可能会写出这样的代码: if (key === 'font.size') { user_config.font.size = value } else if (key === 'page.color.background') { user_config.page.color.background = value } // 更多的 if 语句... 这样写显然不够优雅,看看putValue与pickValue的组合用法。 // 写入用户配置: utils.putValue(user_config, key, value) // 读取用户配置 const value = utils.pickValue(user_config, key) 不管key怎么变,一句话搞定,感觉一下子和大学生拉开差距了是吧? 向数组末尾添加元素:pushValue函数 在实际开发中,常有向数组末尾添加数据的需求。例如记录用户最近的评论,此时你可以使用pushValue函数: const user_data = {} // 用户数据 let comment = {content: '顶'} // 用户的评论 utils.pushValue(user_data, `articles.recent_comments`, comment) console.log(user_data) // {articles: {recent_comments: [{content: '顶'}]}} 在上面代码中,pushValue函数先是自动创建了user_data.articles.recent_comments数组,然后把comment添加到数组末尾。pushValue函数的声明如下: /** * 将值推入对象指定路径的数组中,若路径或数组不存在则自动创建。 * @param {Object} obj - 目标对象。 * @param {string} key - 数组属性的路径,支持'a.b.c'形式。 * @param {*} value - 要推入的值。 * @throws {Error} 如果路径不是数组,则抛出异常。 */ pushValue(obj, key, value){ // ... } 再次调用此函数,数组中就会有两个评论: comment = {content: '再顶'} utils.pushValue(user_data, `articles.recent_comments`, comment) console.log(user_data) // {articles: {recent_comments: [{content: '顶'}, {content: '再顶'}]}} 向对象中添加多个属性:putObj函数 前面我们使用putValue函数向obj对象写入了一个属性值,但如果你要写入很多个(例如 100 个)属性值,你可能会使用for循环: let user_config = {} let new_config_keys // 100 个新的配置项(数组) let new_config_values // 对应的 100 个值(数组) for (let i = 0; i < new_config_keys.length; i++) { utils.putValue(user_config, new_config_keys[i], new_config_values[i]) } 这样写没问题,但在实战中,你拿到的用户配置往往不是数组的形式,而很可能是一个对象,例如: let new_config = { font: { size: 16, 'family.first': 'Arial', 'family.second': 'sans-serif', }, 'page.color.background': '#fff', // ... } // 你对拿到的配置数据又进一步处理 new_config.update_time = new Date() 这种情况下你可以使用putObj函数一次性写入多个属性值: utils.putObj(user_config, new_config) console.log(user_config) /* 输出如下: { font: { size: 16, family: { first: 'Arial', second: 'sans-serif' } }, page: { color: { background: '#fff' } }, update_time: '...', } */ 注意putObj会自动处理上面new_config变量中各种路径的写法。putObj函数的声明如下: /** * 将一个对象的所有属性按路径添加到另一个对象中。 * @param {Object} obj - 目标对象。 * @param {Object} obj_value - 要添加的属性对象,键支持'a.b.c'形式的路径。 * @param {Object} [options={}] - 可选参数。 * - {boolean} remove_undefined - 如果为 true 且 value 为 undefined ,则删除该属性。 * @throws {Error} 如果 obj 为 null 或 undefined ,或路径不合法(如中间非对象)则抛出异常。 * * 注意 * 若 obj_value 中出现重复路径,则后者会覆盖前者。 * 如 obj_value = {a: {b: 1}, 'a.b': 2},则结果为 {a: {b: 2}} */ putObj(obj, obj_value, { remove_undefined = true} = {}) { // ... } 从对象中获取多个属性:pickObj函数 同样的,我们可以一次性读取多个对象的属性值。例如虽然小程序中的用户配置非常复杂,但当前页面仅关注背景颜色、字体大小等少量配置项,你可以这样使用pickObj函数: let user_config // 某个用户的所有配置 // 本页面需要关注的配置 const keys = ['page.color.background', 'font.size', 'font.family'] // 获取当前页面需要的配置 const curr_config = utils.pickObj(user_config, keys) console.log(curr_config) /* 输出如下: { 'page.color.background': '#fff', 'font.size': 16, 'font.family': { first: 'Arial', second: 'sans-serif' } } */ console.log(curr_config.font) // undefined 注意,传给pickObj函数的第二个参数是一个字符串数组,而不是对象。并且,pickObj返回的对象中,属性值不是以curr_config.font.size这样的形式返回,而是返回curr_config['font.size']。 当然,如果你想要curr_config.font.size这样的形式,可用putObj转换一下: let obj_config = utils.putObj({}, curr_config) console.log(obj_config.font.size) // 16 点表示法在微信小程序中实战演示 为什么要设计这几个函数?为什么要支持config.a.b.c与config['a.b.c']两种写法混用?为什么传给putObj的第二个参数是对象,而传给pickObj的第二个参数是字符串数组?为什么pickObj返回的对象属性值不是config.a.b.c这样的形式,而是config['a.b.c']? 因为这样设计符合实战需求,一句话解释就是:“这样好用”。 下面我们通过几个案例来演示这些函数在实战中的应用。 在 js 中设置用户配置 假设用户首次打开小程序,你需要设置用户默认字体大小为 16 ,背景颜色为白色。可以这样写: let user_config = {} utils.putValue(user_config, 'font.size', 16) utils.putValue(user_config, 'page.color.background', '#fff') 使用putValue设置后,你想修改字体大小和背景颜色?可以这样写: user_config.font.size = 18 user_config.page.color.background = '#000' 在 wxml 中实现修改用户配置 你可能会在 wxml 页面中实现多个配置项的修改,并且使用同一个函数来处理。这时你可以这样写: <button bind:tap="changeConfig" data-key="font.size" value="16" > <button bind:tap="changeConfig" data-key="page.color.background" value="#fff"> changeConfig(e){ const { user_config } = this.data const { key, value } = e.currentTarget.dataset // 从 wxml 中获得点表示法的 key 字符串,直接调用 putValue 函数 utils.putValue(user_config, key, value) // 修改背景色时顺便改一下字体颜色(两种写法混用) if (key === 'page.color.background' && value === '#fff') { user_config.page.color.font_color = '#000' } // 记录最近修改时间 user_config.update_time = new Date() } 在 wxml 中使用用户配置 要在页面中使用page.color.background与page.color.font_color的值,实现根据用户配置显示不同的颜色,可以这样写: <view style="background-color: {{color.background}}"> <text style="color: {{color.font_color}}"> Hello, WxMpCloudBooster! </text> </view> onLoad(){ const { user_config } = this.data const color = utils.pickValue(user_config, 'page.color') this.setData({color}) } 你看,我们传递给pickValue的key根据实际需求可长可短。 初始化默认的用户配置 你希望为每个用户设置一个默认的用户配置,并且你想用常规方式写(不使用点表示法)。可以这样: // 默认配置 const DEFAULT_CONFIG = { font: { size: 16, }, page: { color: { background: '#fff', font_color: '#000' } }, // 这里也可以使用点表示法 a.b.c ,但你不想这样写... } App({ initConfig(){ let { user_config } = this.data utils.putObj(user_config, DEFAULT_CONFIG) // user_config 的其他值会被保留 // 保存用户配置... } }) 注意,上面代码中user_config可能会有其他没有出现在DEFAULT_CONFIG中的配置项,这些配置项会被保留。 记录用户最近发表的内容 假如你已经实现了“记录用户最近发布的评论”功能,代码如下: <button bind:tap="append" data-key="articles.recent_comments" data-prop="comment" > 当用户点击这个按钮时,假设this.data中已经有一个comment对象,你可以这样添加评论: append(e){ const { user_data } = this.data const { key, prop } = e.currentTarget.dataset const value = this.data[prop] // prop === "comment" utils.pushValue(user_data, key, value) // 注意这里用的是 push } 上面这个append函数会把this.data.comment对象添加到user_data.articles.recent_comments数组的末尾。 然后,此时你希望再增加一个按钮,可以把最近的点赞数据this.data.like添加到user_data.articles.recent_likes数组的末尾,那么只需一句: <button bind:tap="append" data-key="articles.recent_likes" data-prop="like" > 完成了,你不需要修改append函数,只需要给data-key和data-prop属性设置不同的值即可。 可见,点表示法很大的目的是为了在wxml中可以方便地指定路径,并在js中方便地处理这些路径。 在页面中修改多个配置项 假设你有一个修改用户配置项的页面,wxml代码如下: <!-- 注意这里有一个 for 循环 --> <view wx:for="{{configs}}"> 配置名称:{{item.title}} 当前值:{{item.value}} 输入新值:<input type="text" /> 点击修改:<button bind:tap="changeConfig"/> </view> 上面代码使用了for循环,configs变量中有多少个值,就会显示多少个配置项。 为了实现在用户打开页面时显示的是用户的当前值(而不是默认值),你还需要从user_config中读取当前用户的配置值。 代码样例如下: // 代码中写死了需要修改的配置项以及默认值 configs = [ {title: '字体大小', key: 'font.size', value: 16}, {title: '背景颜色', key: 'page.color.background', value: '#fff'}, {title: '字体颜色', key: 'page.color.font_color', value: '#000'}, ] // 读取当前用户的配置值 const uc_obj = utils.pickObj(user_config, configs.map(item => item.key)) // 注意,这里的 uc_obj 是 uc_obj['font.size'] 这样的形式,而不是 uc_obj.font.size // 用户当前值覆盖默认值 configs.forEach(item => { item.value = uc_obj[item.key] }) this.setData({configs}) // 传给 wxml 页面显示 这样你就实现了修改多个配置项的页面,用户打开页面时显示的是用户当前的配置值。 提问:假设我们坚决不使用点表示法,且要实现上面这些功能,你要如何设计才能如此简单、高效? 让你的函数也支持点表示法 好了,目前我们花了不少篇幅介绍点表示法,这是因为后面我们会介绍更多的工具函数,而这些工具函数都支持点表示法的调用方式。 当你编写自己的工具函数时,你可以调用putValue、pickValue、pushValue、putObj、pickObj这 5 个函数,轻松地让你的工具函数也支持点表示法。如果你不知道如何实现,可以参考utils.js中其他函数的代码。 (文章节选完,如果你感兴趣的话可以看看 Github 项目)
已推荐帖子