2024-09-01

14 篇热帖

1. 毕业 4 年在 Q 音干到 T11,准备跑路了,谈一谈想法

介绍

23 岁的研一暑假,我在校招实习时加入腾讯音乐,那个时候 TME 还属于腾讯的 SNG 。

虽然在那之前我已经是一个在 GitHub 活跃了 5 年有余的独立开发者,但是我从未参与过企业级的项目。通知这次实习,我掌握了项目的完整流程,也学习到了对线上的敬畏、对用户个体的关注。

加入时的期望

在实习之后,我在对鹅厂的这段经历充满热情,加上团队氛围很好,毅然选择留下来,也没有去看其他的机会。

我想在这里收获尽可能多的技术和成长,想与尽可能多的鹅们建立链接——想体验积极向上的团队氛围,想收获一段独特的人生经历。那时,我对自己的职业规划很简单,三年内到 T9 就可以了。

那个时候给自己保存了这句歌词,感谢过去的自己:

职业经历

实际的经历则曲折很多,我加入互联网行业时恰好是动荡期,立马经历了几年疫情,之后又是裁员浪潮。我的业务和团队在五年间不断变化,我也积极在其中寻求成长的可能性。

这段期间,我收获了:

  • 充足的报酬,以及成为鹅厂员工的满足感
  • 软件工程技能,现在基本上任何一个需求抛过来能立马抛出它的实现方案
  • 问题处理能力,在问题定位上形成了自己的一套方法论,得到同事之间认可
  • 高效、有序的规划能力,繁杂的工作培养出来的业务能力,最多同时手上并行七个需求仍然井井有条
  • 坚定的工作态度,培养了在工作时保持专注的能力,在工作中几乎能付出百分百的精力和责任感
  • 钻研的精神,对各端技术都有较为深度的理解,始终保持热爱和好奇
  • 沟通与协调能力,越到后期越注重这个软能力项,在过程中负责过紧迫的大项目,同时协调将近 10 个团队配合项目工作
  • 数据分析能力,生涯中培养了对业务的敏感度,对基本数据有直观上的归因能力,在更深的挖掘能力上也得到了培

但也有我不满意的:

  • 对大厂开始祛魅
  • 团建假期没有了
  • 没有机会培养管理经验,个人成长机会有些不明朗
  • 等等其他不说了

自我反思

哪些地方进展顺利:

  • 我晋升很快,两年 T10 、四年 T11 ,绩效让我几乎一直走绿色通道,三次答辩也是 Top1 ,保持着百分百的通过率
  • 以 T11 作为这段经历的结束已经超预期了
  • 收获了很多出来后不知道有什么用的奖项
  • 负责过 4 个核心项目,而且分别在不同的技术领域
  • 软技能得到了培养和提升,包括沟通、汇报、技术分享等

哪些地方做的不好:

  • 主动性在后期逐渐减弱,工作激情在身边人逐渐离职后开始消磨
  • 推动性较弱,顾及形象,但这点后来想通了,如果都是为了同一个目标奋斗的话,那么对事不对人就好

幸运之处:

  • 初期团队的包容和友善
  • 和设计师合作,写了很多专利
  • 绩效不错
  • 领导们也都很好
  • 接触到了很多不同形态的业务
  • 职业生涯中有半数以上的时间专注技术
  • 得到机会接触并负责不错的技术项目,包括跨端项目以及后续的渲染引擎项目,有机会负责 iOS 项目和 C++ 工程,在前端领域得到了扩展和转型

如果重来,我有什么可以改变的:

  • 做更多的技术输出,目前个人的技术输出偏少了
  • 付出一部分精力在工作之外的技术探索上

更多内容和关于未来规划,可见我昨晚发的博客 《再见了,盛夏》

2. 在新加坡的收入怎么转回国内更划算

求教:在新加坡的收入怎么转回国内更划算

本人从 2023 年开始在新加坡工作,EP 。在新加坡的收入包括 A 公司的薪水,B 公司的投资分红等。求教各位有经验的老板,在新加坡的收入怎么转回国内更划算?

  1. 汇率问题。比如通过工行新加坡转国内工行,今天试了一下按 5.41x 兑换。哪家银行汇率更高?
  2. 限额问题。比如通过上述工行同名转账,额度就是 5 万刀。有哪些可靠的方法可以不受这种限制?国内再开一家 C 公司,然后与新加坡的 B 公司进行交易?这样的话,国内 C 公司的企业所有转个人又还要再交税。

很多这方面的问题,还没有来得及考虑,各位老板多多提醒,谢谢。

3. 深圳电信宽带老用户如狗 169 300M 到底还要不要留着

RT 装了 10 年的宽带还是 169 300M 资费降不下来 网上找了个 99 300M 但是没有公网 IP 每个月可以省 70

另外一个方法是跨省办理宽带 39+30=100M 39+60=300M 39+100=1000M 到底选哪个

4. 5 个前端 JS 函数,只为了优雅解决 a.b.c.d = 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.ba.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不是对象,此时会抛出异常。

另一个是如果valueundefined,则会删除该属性。例如下面的代码:

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 语句...

这样写显然不够优雅,看看putValuepickValue的组合用法。

// 写入用户配置:
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.cconfig['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.backgroundpage.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})
}

你看,我们传递给pickValuekey根据实际需求可长可短。

初始化默认的用户配置

你希望为每个用户设置一个默认的用户配置,并且你想用常规方式写(不使用点表示法)。可以这样:

// 默认配置
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-keydata-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 => {
  if (uc_obj[item.key] !== undefined) {
    item.value = uc_obj[item.key]
  }
})

this.setData({configs}) // 传给 wxml 页面显示

这样你就实现了修改多个配置项的页面,用户打开页面时显示的是用户当前的配置值。

提问:假设我们坚决不使用点表示法,且要实现上面这些功能,你要如何设计才能如此简单、高效?

让你的函数也支持点表示法

好了,目前我们花了不少篇幅介绍点表示法,这是因为后面我们会介绍更多的工具函数,而这些工具函数都支持点表示法的调用方式

当你编写自己的工具函数时,你可以调用putValuepickValuepushValueputObjpickObj这 5 个函数,轻松地让你的工具函数也支持点表示法。如果你不知道如何实现,可以参考utils.js中其他函数的代码。

(文章节选完,如果你感兴趣的话可以看看 Github 项目

5. 职业现状与转行机会

我的职业现状

毕业后我就在杭州工作,到现在快 10 年了。前几年在一家中厂上班,由于受不了频繁的加班,降薪跳槽到了现在的公司,公司大约有 100 名员工,我从事前端开发工作,双休不加班,年收入大概在 20 万元左右,骑车到公司大概 8 分钟。公司是以项目为主,目前看这个项目还能维持两三年,但之后的情况不确定,没有接到新项目估计就要裁员了。

新的转行机会

最近有一个转行的机会:我的亲戚在老家办厂,现在想在杭州开设一个电商部门,邀请我去负责天猫旗舰店,主要产品是低压电器。工资待遇按我现在的水平,发展得好还有分红。他计划一年投入 100-200 万元,用于人员和流量购买,办公地点大概会安排在余杭阿里附近,而我现在住在滨江。

犹豫与考虑

现在有些纠结,电商已经发展了这么多年,现在做这个是不是有点晚了?新的工作对我来说也是一个挑战。但亲戚希望做天猫旗舰店,因为目前天猫上有一家店铺没有工厂,所有货物都从他那里拿货,一年销售额能达到两三千万。所以,论坛的老哥们帮忙出出主意,这个机会是否值得抓住?

6. 家有学生,需要打印的资料非常多,哪款激光打印机性价比高?主要考虑耗材,平均大概几毛一页?
7. 北京的消费券大伙都买了些啥呢?

目前支持的品类有:冰箱、洗衣机、电视、空调、电脑、热水器、灶具、吸油烟机。
冰洗空和电视这类大家电有什么值得买值得换的?
电脑这个品类目前比较划算的可能是 R9000P ,但已经直接卖断货了……

8. 求一个 im 软件,替代 telegram

号被封的遭不住,其他都不在乎,主要是 telegram 传文件实在方便,有类似的替代品么?主要是当作文件传输助手,QQ/微信实在难绷

9. 感觉 openclash 的设置好反人类,一个自定义规则搞了好久

一直以为在配置管理里的配置文件里编辑,编辑好自定义的规则是可以使用的,但应该是定制了自动更新订阅的缘故,订阅更新后自定义的规则消失了,现在完全不懂在哪里添加自定义规则,太难了。

10. 疑似有人用“轻松筹”等爱心方式诈骗

事情经过是这样

昨天有人在宝妈宝爸群里发一个爱心求助

然后到晚上的时候,有邻居质疑,怀疑说轻松筹现在有很多骗局,然后一石激起千层浪,邻居们纷纷开始说话,说在很多群都碰到同样的文案,然后这个号一直也不出来说话

像机器人一样,从中午到下午发了 3 次一模一样的文字加红包

然后就有邻居就说现在就搜索聊天记录

轻松筹链接如下

请大家不要捐款!!! 请大家不要捐款!!! 请大家不要捐款!!! https://m.qsmutual.com/fund/91/detail?projuuid=530f9aa2-e6a4-4997-bb9d-0881f8277ea2&shareuuid=0f104c76-260e-474d-b744-c6e5276183ed&share_no=191a68ac59fbd-0561431b50eb42-7e78772b-54600-191a68ac5a054b&level=1&shareto=2&sharecount=1&platform=wechat&godeviceid=1918c6bd9f9749-0fb7bd7ce2f2cb-7a733505-54600-1918c6bd9fa70c&bi_cf=share.link.weiai

问题是这个求助只有病友认证,没有亲属认证

邻居又搜了一下这个医院,

11. Android 系统启动为什么这么慢,到底在做什么?为什么做不到 2 秒内冷启动?
12. Thinkbook 14+ 品质探究
最近薅了京爷券,买了 Thinkbook 14+锐龙版,看到京东自营差评一堆


例如:
ABCD 面做工:C 面键盘中间鼓两边凹、屏幕合不上有缝隙、D 面后盖按压合上松开又顶起、D 面不平整
屏幕:闪屏(屏幕背光供电高压板不稳定,等管接头处焊点不牢固打火)、漏液。亮屏状态下挪动笔记本,小角度开合屏幕屏幕闪屏
兼容性:双盘位(致态 7100 等)睡眠唤醒掉盘
充电接口:用一个月无法充电,用几次无法充电,松,易晃动
系统:黑屏崩溃
其他:卡顿,发热,噪声大,电流声,触控板就不在列举了

请教各位大佬,你们买联想的本有过类似问题么?和京东自营客服 battle 退换货难度高不高?
13. 日本 NTT 开发的最新光通信技术前两天在东京和台北之间开通了
新闻链接 https://news.yahoo.co.jp/articles/c37abb7244e40dc23bc5fa006bb72e6bc2c8bb49
大致看了一下,说 NTT 开发的名叫 IWON 的次世代的光网络在 NTT 东京研发中心和台北的中华电信桃园数据中心相连,单程延迟 16.92ms 也就是日台延迟在 34ms 左右。现在日台延迟普通在 40 到 45ms 左右,是要快了一点。带宽则是 100gbps 。还有点离谱是的说消费的电量是现行的 200 分之 1 ,这是真环保啊
14. GPT 4 现在变得真的好垃圾,质量越来越差

速度是越来越快,质量是越来越差,还不听指令,瞎几把乱答。 免费用户的体验确实上来了,但这 plus 就一点用都没有了。无论是哪个模型,极其简单的逻辑、计算都能回答错误,以后不会再续了😅