2025 week 2
2025 week 2 陪家人去医院请了几天假,工作输入输出都没什么进展。不过这段时间因为 deepseek 的火热,感觉让世界看到了中国科研的潜力,国内大家的信心也得到重振,于是建仓了恒生科技。 娱乐 《网络迷踪》⭐️⭐️⭐️⭐️ 还不错的悬疑片,虽然是好几年前的电影了,但表现手法现在的眼光来看依然很新颖。通过电脑上摄像头的视角以及桌面的录制,来讲述一个父亲寻找自己失踪女儿的故事。同时也算是一部社会工作的科普片?
2025 week 2 陪家人去医院请了几天假,工作输入输出都没什么进展。不过这段时间因为 deepseek 的火热,感觉让世界看到了中国科研的潜力,国内大家的信心也得到重振,于是建仓了恒生科技。 娱乐 《网络迷踪》⭐️⭐️⭐️⭐️ 还不错的悬疑片,虽然是好几年前的电影了,但表现手法现在的眼光来看依然很新颖。通过电脑上摄像头的视角以及桌面的录制,来讲述一个父亲寻找自己失踪女儿的故事。同时也算是一部社会工作的科普片?
为了更好地通过记录这个行为来感知时间(开始写周记也是这个目的),研究了一下如何通过 toggl 的 API,现在可以通过 Shortcuts 自动将使用手机上的各种 app 自动分门别类(购物,社交网络,理财)记录使用时间,并且将低于 2 分钟的使用自动归类为 distraction 了。等这套系统再跑一跑完善一下,再来写一下体验和心得。 工作 年前被告知晋升没通过,理由是尽管对于交付工作和团队管理都很满意,但因为缺少了在团队之外稳定地输出影响力的事实,所以给拒绝了。得知这一消息多少有点打击积极性,但自己接受得比想象的快,可能和我司晋升之后对薪资涨幅的提升不太明显有关。 25 年春节后开工第一周,第一天几乎没怎么工作,主要是回忆去年我到底是干什么的。然后第二天生产力爆棚,酣畅淋漓地写了 6 个多小时代码。 娱乐 这周大部分时间都是放假,看了三部电影一部美剧。 《最后的生还者》⭐️⭐️⭐️⭐️⭐️ 无论是人物场景还是剧情对于游戏都是相当好的还原,甚至有超越。但到了长颈鹿的名场面,观察了一下身边人的反应,似乎没有自己当初见到这个场景时所感受到的情绪来得复杂,以及 Sam 自杀之后的那个长长的黑屏时电视倒影里出现自己错愕表情时候的冲击。如果可以,还是推荐体验游戏。代入感会强很多。 《哪吒 2》⭐️⭐️⭐️⭐️⭐️ 记得对第一部的评价是过誉了,但这第二部看得是相当爽。国产动画真棒啊。 《好东西》⭐️⭐️⭐️⭐️⭐️ 观影过程中有被剧中的单亲妈妈和小孩的相处模式触动到,但两天后已经回忆不起来大部分的剧情细节了,怀疑和电影里角色对话的台词并不像正常人说话那样有关(太过于书面化了)。 《封神 2》⭐️⭐️ 特效很差,且剧情比较磨磨叽叽。值得一提的是邓婵玉这个角色虽然有一条莫名其妙的感情线,但表现依然很出彩,大陆女演员似乎没多少这个类型的。 输入 看书时间比较少,仅仅 4 小时。主要是去年没看完的《理性思考的艺术》以及《正面管教》。有个变化是听了 nice try podcast 中 cbvivi 说自己坚持了一年时间,晚上睡前不带电子产品进到卧室,选择睡前看纸质书,于是乎准备尝试一下。 还和 deepseek 聊天聊了 2 小时,感觉用过深度思考模式就回不去了。无奈 deepseek 服务太不稳定,尝试了一下国内的硅基流动也相当慢,基本处于不可用状态。从 X 搜索了一下相关信息,简单调查之后找到了 openrouter.ai 这个服务,搭配 OpenCat 使用效果还不错。
看到 cbvivi 发的 “2024 年玩乐时间” ,才想起看一眼自己这一年在 timery 上的记录。 总共记录在册的时间是 936 小时,主要记录了游戏(396 小时),健身(258 小时),阅读(81 小时)三个大项和其他小项。翻看时才发现自己只记录了游戏的具体项,而阅读则仅仅区分为了漫画(28 小时)和书籍(53 小时),同时还缺少了在工作,视频和文章阅读上消费记录,有点遗憾。 记录是可以让人更清晰感知时间的一种方式。 游戏 花费了 396 小时,比去年少了 41%。平均每次 1 个小时 41 分钟。 有了小孩之后,游戏往往是有了相对来说的大段时间才会启动,所以单次持续时间相对来说会长一些。排在前二的是《战双帕弥什》(127 小时)和《尘白禁区》(75 小时)两个手游,第三的是《塞尔达:王国之泪》(64 小时)。回顾起来,对这两款手游带来的快乐时光几乎没有什么记忆了,只记得抽卡和重复日常任务带来的糟糕感受,但操纵林克四处探索解密发现新大陆的兴奋感依然历历在目。 所以今年应该会考虑更多地投入到主机游戏,而不是手游这样耗费大量时间进行重复劳作的游戏类型了。不过似乎每次放弃一款手游时,都会这么说,然后过不了多久又开了一个新的手游坑,或者是回归到某个手游上。今年的《战双帕弥什》就是再次回归,《原神》也回归了 26 小时(22 年 344 小时,23 年 383 小时)。这种时不时想玩手游的冲动,想来和手游打开之后只要进行操作,就一定能获得一些即时反馈有关。主机游戏带来的反馈并不那么确定,比如魂系游戏,可能半小时的战斗一无所获,留下的只是挫败感和对那个 boss 的一些不易感知的熟悉度增加。 健身 花费 258 小时,比去年多了 9 %。平均每次 54 分钟。 前一年工作因为要处理时差的关系,所以把健身挪到了早上起床后的第一件事,这一习惯得以保持,目前感受很好。得益于训记 的 apple watch 版本,在组间休息拿起手机刷社交网络的坏习惯也终于得到了改正。 阅读 花费了 81 小时,比去年少了 22%,平均每次 38 分钟。 单次持续 38 分钟和地铁通勤时间高度相符,确实大部分时间看书都是在地铁上,单程通勤 40 分钟。数据上看读书少了一些,豆瓣记录看完了 8 本。每周还不到两小时,和体感不是太相符,可能是因为花了很多时间在各种文档上,今年打算把这部分时间也记录下来验证一下。 PARA 系统 We can endure quite a bit of stress and frustration in the short term if we know it’s leading somewhere. ...
该文已同步发布至 Thoughtworks Insights – What benefits does SwiftUI offer for building a design system? 原文链接 SwiftUI 自 iOS 13 发布以来,虽然已经面向公众近 4 年,但由于在实现复杂布局时的性能不佳,以及因其内置组件的底层实现变更(iOS 16 上 List 的底层实现从 UITableView 改成了 UICollectionView ),导致开发者们原本良好运行代码随系统升级被破坏了。iOS 14 之前 SwiftUI 的开发者体验也让人一言难尽。尽管有很多的槽点,但我们还是能发现社区整体上还是比较接纳 SwiftUI。所以如果你对 SwiftUI 还有所犹豫,不清楚为何要使用它,这篇文章或许能够带来一些新的想法。 本篇文章主要是想要通过 Design System 为切入点,同大家讨论相比起 UIKit,为什么更推荐使用 SwiftUI 来实现大多数业务场景下的 UI 组件。 首先简单概括一下 Design System 是什么,Design System 是一个包含了设计原则、组件库和代码资源等系统化的指导,旨在促进团队间的协作和提高项目的一致性。它可以帮助团队更快速、高效地构建应用程序,同时确保应用程序的外观和交互保持一致。 接下来就进入正题,从以下几点来探讨一下通过 SwiftUI 构建 Design System 有哪些优势。 声明式语法会更具有可读性和易于实现 内建的一致性和统一性表达 单向数据流带来的可预测性 与 Design System 有更相似的哲学思想 声明式语法会更具有可读性和易于实现 首先从实现和维护成本上来说,SwiftUI 与 Apple 现有的 UIKit 和 AppKit 不同,SwiftUI 采用了声明式语法构建 UI。由于声明式语法更关注于描述 UI 的最终效果,而不是具体实现方式。这使得通过声明式语法编写的 UI 组件更具可读性,有助于团队更好地协作实现 Design System。 ...
经历过的 Mobile 项目基本上都有支持 Deep Linking 的需求,每次新项目都会经历向其他端同事和 BA 解释实现 Deep Linking 两种方案的区别,于是就有了这一篇短文。主要是介绍两种方案的优缺点和实现成本差异,并不涉及如何实现的代码步骤。 首先什么是 Deep Linking,简单来说就是让一个 App 可以通过 URL 打开其他的 App,以提供更便捷高效的用户体验。 如果想跳到别人那去 BA 老刘:「如果想在 App 里打开人家的 App 应该怎么做呢?」 开发小曾:「目前有两种选项,URL Scheme 和 Universal Links。」 BA 老刘:「区别是啥?」 开发小曾:「主要看你想不想处理用户没有安装对方 App 的情况。」 ps: Android 中这两种选项是 Deep Links 和 App Links,运作原理大体相同,实现方式 iOS 和 Android 有些许差异。为少打字,下文将统一使用 URL Scheme 和 Universal Links。 URL Scheme 通常长这样: example://destination?param1=hello。 想通过 URL Scheme 跳转到某个 App,我们需要知道对方 App 定义的 Scheme 是什么(这不算是废话)。 以 Twitter 为例,如果当前设备安装了 Twitter: 通过 twitter://user?screen_name=elonmusk 这样一个 URL Scheme,就可以打开 Twitter 并且跳转到 Elon Musk 的主页(scheme 可以输入 Safari 地址栏进行测试)。 ...
从朋友圈看到思琦发了一个《使用 SwiftUI 打造卓越体验》的 Apple 开发者线上活动的报名链接,刚好最近参与的项目也在大规模使用 SwiftUI 就报名了,即使时间很不凑巧是工作日(另一报名要求是必须要有中国区的开发者账号)。 活动分两天第一天主要是一些 SwiftUI 的介绍,SwiftUI 对数据的处理和布局的一些要点。第二天是社区开发者交流。 技术细节后面可以分好几篇博客来描述,但总的来说技术相关的收获其实和自己去看 WWDC 差不多。并且由于长期以 2x 速度观看这类视频,刚开始还出现了一些不适应。但想着这是来自苹果的开发者的分享,还是管住了想拿起手机的手,毕竟机会难得。 事实证明确实认真观看还是有收获,主要是以下几点: 知道了一些 SwiftUI 相关的 Xcode 快捷操作,比如 Preview 和代码其实是可以相互影响的。Library 中可以搜索 ViewModifier 等等。 来自苹果的工程师对 SwiftUI 这样响应式 UI 编写方式的数据流思考的建议。 一些编写 SwiftUI 代码时能够让渲染引擎更高效的建议。 除了上面开发者分享的内容之外,最重要的是可以提出一些自己在使用 SwiftUI 时遇到的问题,感觉只要描述得够清楚并且和本次活动主题相关,那么都能够得到苹果开发者的解答,虽然受形式或时间限制,答案并不一定完整,但当一个 SwiftUI 的疑问是由苹果的工程师解答时,即使只是关键字的指引也能起到画龙点睛的作用(比如 StateObject 与 ObservedObject 的差异)。 第二天更多的是听社区开发者的一些交流,收获到了不少之前并不知道的资料。一并列在下方。 ps:感觉参加这次活动对自己来说还有一个副作用就是,由于资料的推荐者有足够的可信度,并且提供了一个可靠的学习路线,所以会用格外的认真来对待它们。 Swift UI 的入门指引 WWDC - Introduction to SwiftUI WWDC - SwiftUI Essentials 十个很有代表性的 Playground 交互式的 SwiftUI 入门教程 使用 SwiftUI 构建完整 App 的交互式教程 SwiftUI 数据处理要素 ...
在 iOS 业务开发过程经常面对网络请求,数据持久化这样带有副作用的操作。为了能够在测试中 mock 这些操作,通常的做法就是抽象一层 protocol 出来,然后编写不同的实现。 比如需要处理一个简陋的注册业务(示例省略了一点细节),需要用户输入信息后发送网络请求,成功后返回对应用户对象。 首先为网络请求定义一个 protocol: protocol SignUpRepositoryProtocol: RepositoryProtocol { func handleSignUp(name: String, email: String, pwd: String) -> AnyPublisher<User, Error> } 对其进行实现: struct SignUpRepository: SignUpRepositoryProtocol { func handleSignUp(name: String, email: String, pwd: String) -> AnyPublisher<User, Error> { client(.signUp(name, email, pwd)) .map .decode .eraseToAnyPublisher() } } 将其注入到 ViewModel 或是 Interactor 中(取决于你采取的架构是什么 :p ),并且调用对应方法: class SignUpViewModel { enum State { case loading case success case failed } let repository: SignUpRepositoryPortocol var state: State = .loading init(repository: SignUpRepositoryPortocol) { self.repository = repository } func onSubmit(name: String, email: String, pwd: String) { repository.hanldeSignUp(name: name, email: email, pwd: pwd) .sink {[weak self] completion in switch completion { case .failure: self.?state = .failed case .finished: break } } receiveValue: {[weak self] result in self?.state = .success }.store(in: &bag) } } 由此如果需要测试对应的方法,只需要再创建一份 MockSignUpRepository 的实现即可,比如想要测试注册成功或失败场景下的处理: struct MockSignUpRepository: SignUpRepositoryPortocol { let shouldSignUpSuccess: Bool func handleSignUp(name: String, email: String, pwd: String) -> AnyPublisher<User, Error> { if shouldSignUpSuccess { Just(User.mock) .mapError{ _ in SignUpError.someError } .eraseToAnyPublisher() } else { Fail(error: SignUpError.someError) .eraseToAnyPublisher() } } } 在编写测试的时候,传入 SignUpViewModel 的依赖替换成我们想要测试的 Mock 实现: ...
因为项目需要使用 SwiftUI,想起来之前买过喵神的 《SwiftUI 与 Combine 编程》 。书中介绍了 Redux 这一在 Web 前端领域广泛被验证过的数据管理模式是如何通过 Swift 来实现的,非常推荐 SwiftUI 初见者阅读。 在学习过程中还产生了一个疑问,如果 reducer 越来越大,有什么更 “swift” 的办法能解决这一问题呢?(在 Redux.js 中的原生解决方案是 combineReducers) 拆分 Reducer 首先看看问题在代码中的表现是什么样的,假设我们有这样一个 reducer: func appReducer(appState: inout AppState, action: AppAction) -> Void { switch action { case .emailValid(let isValid): appState.settings.isEmailValid = isValid case .register(let email, let password): appState.settings.loginUser = User(email, password) case .login(let email, let password): appState.settings.loginUser = User(email, password) case .logout: appState.settings.loginUser = nil case .loadPokemon(let result): appState.pokemonList.pokemons = result case .favoratePokemon(let pokemon): appState.favoritePokemons.append(pokemon) case .removeFavoritePokemon(let pokemon): let index = appState.favoritePokemons.indexOf(pokemon) appState.favoritePokemons.remove(at: index) } 应用的 action 主要包含三个模块: 账号登录注册注销 对神奇宝贝数据进行加载 处理对神奇宝贝数据的收藏和取消收藏 从这段代码我们很快就能发现,即使只是非常简单的示例也已经包含了不短的代码了。这里还省略掉了处理状态时可能还需要的异步 action 的处理(数据加载等)。这还仅仅只有两个非常简单的界面状态,当面对真实的 app 所需要处理的数十个页面状态会更恐怖。 将 reducer 拆分成如下三个独立 reducer: func accountReducer(appState: inout AppState, action: AppAction) -> Void { switch action { case .emailValid(let isValid): appState.settings.isEmailValid = isValid case .register(let email, let password): appState.settings.loginUser = User(email, password) case .login(let email, let password): appState.settings.loginUser = User(email, password) case .logout: appState.settings.loginUser = nil default: break } func pokemonListReducer(appState: inout AppState, action: AppAction) -> Void { switch action { case .loadPokemon(let result): appState.pokemonList.pokemons = result default: break } func favoritePokemonReducer(appState: inout AppState, action: AppAction) -> Void { switch action { case .favoratePokemon(let pokemon): appState.favoritePokemons.append(pokemon) case .removeFavoritePokemon(let pokemon): let index = appState.favoritePokemons.indexOf(pokemon) appState.favoritePokemons.remove(at: index) default: break } 因为对 reducer 的数量并不确定,所以这里使用可变参数来构建 combine 方法,对传入的 reducer 进行遍历调用处理 appState。 ...
在 SwiftUI 中,需要通过数据来驱动 UI 的变化。数据结构抽象描述的质量也影响着我们对 SwiftUI 界面的维护。 通常数据中可能存在很多状态,如果使用很多的 boolean 值来描述这些状态,那么 App 的可维护性可能会大大降低。 管理独立状态的问题 假设我们有一个 App,用户可以在登录与非登录状态下进行操作。所以我们的界面需要兼容这两种状态,其描述可能是这样的: class AppState: ObservableObject { @Published var user: User? = nil @Published var error: Error? = nil var authenticated: Bool { user != nil } var hasError: Bool { error != nil } } 基于这样的状态描述,如果我们想创建一个仅展示用户名的组件大概会是这样: var body: some View { Group { if state.hasError { Text("Oops, sth went wrong: \(state.error!.localizedDescription)") } if state.authenticated { Text("Hello \(state.user?.name ?? "Unknown")!") } else { Text("Hello, stranger") } } } 粗看没有什么问题,实际上在维护这样的数据结构时就需要格外小心了。比如第一次我们登录失败,为了展示错误信息给 error 设置了值之后。必须在登录成功之后要及时地去清空 error,否则即使 state.authenticated 等于 true,用户依然无法看到正确的信息。 这还仅仅是有两个状态的情况下,像这样独立状态属性会带来很大的维护成本,开发者需要牢记各个属性之间的依赖关系,甚至编写界面的时候,还需要注意代码执行顺序。 引入状态机 把状态抽象成带有 associated values 的 enum 是个更好的选择,比如: class AppState: ObservableObject { enum AccountState { case authenticated(User) case unauthenticated case error(Error) } @Published var accountState: AccountState = .unauthenticated } // 界面中的使用 var body: some View { VStack { switch state.accountState { case .authenticated(let user): Text("Hello \(user.name)!") case .unauthenticated: Text("unregister") case .error(let error): Text("Oops, sth went wrong: \(error.localizedDescription)") } } 这样被状态机驱动的界面看起来要直观多了。并且在每个状态中对数据的操作,也由 enum 赋予了隔离能力。 ...
先看下面的代码 function printHello() { console.log("Hello"); } function printWorld() { console.log("World"); } printHello(); // 输出 Hello printWorld(); // 输出 World 在 JavaScript 中,存在一个全局调用栈(Global Call Stack)。当我们调用 printHello 时,会将该方法加入到栈中,由于 JavaScript 是单线程执行机制(同一时间只执行一个命令),所以会在执行完成了 printHello 之后再执行 printWorld。 那么现在就引入标题中的问题,JavaScript 既然是单线程语言,为什么 setTimeout 不会阻塞线程? function printHello() { console.log("Hello"); } function printWorld() { console.log("World"); } setTimeout(printHello, 1000); printWorld(); 表面上来看 setTimeout 也是一个方法,他的定义可能是这样: function setTimeout(callbackFunc, interval) { // .... } 那么按照 JS 单线程理论来说,应该是先将 setTimeout 方法压入全局调用栈,并且执行该方法,等待 1 秒钟,然后再执行 printWorld 才对。但实际上我们都知道,打印的结果会是 “World” 然后 “Hello”,这是为什么? Web Browser API & Callback Queue 事实上 setTimeout 并不是完全是 JS 代码,而是属于 Web Browser API 中的方法。就像名字中所指的那样, JS 调用了 setTimeout 之后,浏览器(Web Browser)会去创建一个 timer,同时将我们传入 setTimeout 的方法 - printHello 加入到 Callback Queue(回调队列) 中。 ...
复合组件是什么 编写页面时,经常存在多个子组件的展示,是依赖于同一个数据源的情况。 比如单选框: <Switcher> <Switch on={this.props.selecting == 'React'}>React</Switch> <Switch on={this.props.selecting == 'Vue'}>Vue</Switch> </Switcher> 我们可以看到,所有的 Switch 的数据都需要对 selecting 的值进行判断,并且代码中其实只有 this.props.selecting == 后面的部分不同,如果能改写成这样: static Switcher.React = ({selecting}) => ( <Switch on={selecting === 'React'}>React</Switch> ) static Switcher.Vue = ({selecting}) => ( <Switch on={selecting === 'Vue'}>Vue</Switch> ) <Switcher selecting={this.props.selecting}> <Switcher.React/> <Switcher.Vue/> </Switcher> 隐式地将父组件的数据传递给子组件,其显示逻辑交由给子组件自行处理,代码的组织结构将会清晰很多。后续即使需求变动,数据的传递改变也并不需要我们操心(不需要一个子组件一个子组件地添加传递),只需要修改 Switcher 子控件内部处理逻辑即可。 那么要怎么实现这个隐式数据传递呢? 可以通过 React.Children.map 和 React.cloneElement 这两个 API 来实现。 React.Children.map 与 React.cloneElement 在 render 中我们可以使用 React.Children.map 来获取到 Switcher 中的子组件,然后通过 React.cloneElement 对组件进行克隆及数据传递: render() { return React.Children.map(this.props.children, child => React.cloneElement(child, { on: this.state.on, toggle: this.toggle, }), ) } 这样,即使我们在使用 Switcher.React 和 Switcher.Vue 时,没有显式地传递参数,子组件也能获取数据。 这里 React.Children.map 与 this.props.children.map 并不等价,后者在只有一个子组件的时候,返回的不是数组,而是唯一的那个组件。 React.Children.map 的局限性 上面代码有个问题是,如果出现了更多层级的子组件,那么参数传递只会到第一层。 <Switcher selecting={this.props.selecting}> <Switcher.React/> <div> <Switcher.Vue/> </div> </Switcher> 这样写会提示传递了错误的参数给 div,因为我们 React.Children.map 只能获取到第一层子组件([Switcher.React, div])。 ...
翻译自 Optimizing Images by Jordan Morgan 有句话说:最好的照相机就是在你身边的那台。 如果这句俗语是对的,那么毫无疑问地— iPhone 是这个星球上最重要的相机, 并且我们的业界也证明了这一点。 在度假中? 如果没有在你的 Instagram Story 中留下几张照片,那就不算发生过。 爆炸新闻? 立刻打开 Twitter 来查看哪些媒体在通过照片实时报道事件。 等等。 由于图像在各个平台无处不在的出现,在低性能且内存紧张的情况下展示它们,会很容易地造成失控。 如果我们知道 UIKit 底层到底发生了什么,为什么以及如何处理图像,那么我们可以节省大量的资源开销,并且逃脱无情的系统清除制裁。 理论上来说 突击测验 - 这张我女儿的 266 KB 字节大小(并且还蛮时尚的)的照片,在一个 iOS App 中会展示需要用到多少内存? 剧透一下 - 不是 266 KB,也不是 2.66 MB,而是大概 14 MB。 为什么? 本质上来说 iOS 申请内存是根据图像的尺寸 - 而图像的文件大小反而影响不大。 这张图片的尺寸是 1718 x 2048 像素。 假设每个像素会占用 4 个字节: 1718 * 2048 * 4 / 1024 / 1024 = 13.42 MB 大约 ...
SwiftUI 在 View 层级提供了四种特性: Declarative 通过声明式的语句来描述 UI 布局, 样式, 动画等. Automatic 可交互形动画, 动态字号, 夜间模式都可以通过配置来轻松实现. Compositional 组合性. 各种控件都能极其方便地组合在一起, 远比 UIStackView 方便. VStack(alignment: .leading) { Text(item.title) Text(item.subtitle) } Consistent 自带 Reactive 特性. 将 Model 对象继承自 BindableObject, 并且声明属性为 @State 即可获得当属性改变时, UI 控件自动更新的效果. 真的如果如此美好, 超级吃性能的 xib 和 storyboard 是不是可以退出舞台了. Xcode 11 Live Development 直接在 Xcode Preview 中拖动控件即可生成对应的 SwiftUI 代码. 对应的修改 SwiftUI 代码也能实时在 Preview 中响应. Preview 还能通过提供一个 PreviewProvider 来为其提供数据填充展示, 样式更改甚至循环语句来生成多个 Preview 同时查看控件在夜间模式和白日模式下不同的效果. Preview 部署在设备上也能热加载. Package Management Swift 终于有自己的 Package manage 了. 并且和 Xcode 进行了深度整合. ...
JS 在 ES6 的中新增了函数参数指定默认值的支持: const Greeting = (name="Joeytat") => { console.log(`Hello ${name}`) } Greeting() // Hello Joeytat 那我们就可以利用这一特性, 将一个会抛出异常的方法作为默认参数传递. const Greeting = (name=EmptyPropertyException("name")) => { console.log(`Hello ${name}`) } const EmptyPropertyException = (propertyName) => { throw Error(`${propertyName} 为必填参数`) } Greeting() // 抛出异常: "Error: name 为必填参数" 这样如果没有传递参数就会抛出异常, 并且带有友好的提示了.
刚毕业那阵儿还每年都写好长的总结, 然后这两年变懒了. 今年又想再记录一下了嘿 | ᐕ)⁾⁾ 年度 App: 多邻国 零基础学语言的感觉很不错. 年度电影: 《三块广告牌》 年度漫画: 《只有我不存在的城市》 忘了在哪被人推荐的了, 被安利到的话大概是这么说的「非常庆幸在没有被剧透的情况下一口气看完了」. 看完了之后感觉果然如此. 而且漫画真是存在着动画无法表现出的节奏感啊. 年度游戏: 《神界原罪 2》 接触的第一款 CRPG, 有趣到什么程度呢? 从游戏体验时发出「wow, 居然还能这样?」的频率来看, 和《塞尔达: 旷野之息》差不多吧. 年度虚构类图书: 《剑来》 不知道为啥现在提起网络文学, 多数时候对方都还是觉得「格」不够. 可现在的网络小说与《明报》连载的武侠小说, 有多大的区别? 年度非虚构类图书: 《邻人之妻》 我姓王和我看这本书没有任何联系(认真脸), 真的是好奇性美国解放运动到底是怎么产生的而看的. 年度电器: Sony 9000E 电视 大屏幕 4k HDR 的全新体验让我想把之前在显示器上玩的好游戏看的好电影都重新来一次. 年度音乐: 《生きていたんだよな(她曾活过啊)》 歌词很棒? 看到年度音乐四个字, 脑子里第一首出现的歌. 年度视频: 井越的 vlog——《别再问我什么是 2017》 看了几十个 Casey Neistat 的 vlog 也没能让自己行动起来用视频记录生活. 但看完这个视频的第二天, 就开始尝试着在大街上对着手机镜头说话啦. 年度电子产品: iPhoneX 用来拍了不少视频, 照片. 人像模式拍出的照片, 会让我这个摄影门外汉产生一种「自己拍的还不错嘛」的错觉, 从而达到了要经常掏出手机记录生活的目标. 希望明年也能多创造一些东西, 能够在 19 年的年终总结中, 选出自己满意的年度 XXX 吧