diff --git a/TODO1/5-best-practices-to-prevent-git-leaks.md b/TODO1/5-best-practices-to-prevent-git-leaks.md index 933e9f236a9..24f7fe096f2 100644 --- a/TODO1/5-best-practices-to-prevent-git-leaks.md +++ b/TODO1/5-best-practices-to-prevent-git-leaks.md @@ -1,88 +1,90 @@ -> * 原文地址:[5 Best Practices To Prevent Git Leaks](https://levelup.gitconnected.com/5-best-practices-to-prevent-git-leaks-4997b96c1cbe) -> * 原文作者:[Coder’s Cat](https://medium.com/@coderscat) -> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) -> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/5-best-practices-to-prevent-git-leaks.md](https://github.com/xitu/gold-miner/blob/master/TODO1/5-best-practices-to-prevent-git-leaks.md) -> * 译者: -> * 校对者: +> - 原文地址:[5 Best Practices To Prevent Git Leaks](https://levelup.gitconnected.com/5-best-practices-to-prevent-git-leaks-4997b96c1cbe) +> - 原文作者:[Coder’s Cat](https://medium.com/@coderscat) +> - 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> - 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/5-best-practices-to-prevent-git-leaks.md](https://github.com/xitu/gold-miner/blob/master/TODO1/5-best-practices-to-prevent-git-leaks.md) +> - 译者:[YueYongDEV](https://github.com/YueYongDev) +> - 校对者:[Roc](https://github.com/QinRoc)、[icy](https://github.com/Raoul1996) -# 5 Best Practices To Prevent Git Leaks +# 防止 Git 泄漏的 5 种最佳做法 -![Photo by Clint Patterson on Unsplash](https://cdn-images-1.medium.com/max/4000/0*bskmb4Tr98q5if_y.jpg) +![](https://cdn-images-1.medium.com/max/4000/0*bskmb4Tr98q5if_y.jpg) -Countless developers are using Git for version control, but many don’t have enough knowledge about how Git works. Some people even use Git and Github as tools for backup files. This leads to information disclosure in Git repositories. [Thousands of new API or cryptographic keys leak via GitHub projects every day.](https://www.zdnet.com/article/over-100000-github-repos-have-leaked-api-or-cryptographic-keys/) +无数的开发人员正在使用 Git 进行版本控制,但是许多开发人员对 Git 的工作方式并没有足够的了解。有些人甚至将 Git 和 Github 用作备份文件的工具。这些做法导致 Git 仓库中的信息遭到泄露,[每天都有数千个新的 API 或加密密钥从 GitHub 泄漏出去](https://www.zdnet.com/article/over-100000-github-repos-have-leaked-api-or-cryptographic-keys/)。 -I have been working in the field of information security for three years. About two years ago, our company had a severe security issue triggered by the information leak in a Git repository. +我在信息安全领域工作了三年。大约在两年前,我们公司发生了一起非常严重的安全问题,是由于 Git 仓库发生了信息泄露导致的。 -An employee accidentally leaked an AWS key to Github. The attacker used this key to download more sensitive data from our servers. We put a lot of time into fixing this issue, we tried to find out how much data leaked, analyzed the affected systems and related users, and replaced all the leaked keys in systems. +一名员工意外地在 Github 上泄露了 AWS 的密钥。攻击者使用此密钥从我们的服务器下载很多敏感的数据。我们花了很多时间来解决这个问题,我们试图统计出泄漏了多少数据,并分析了受影响的系统和相关用户,最后替换了系统中所有泄漏的密钥。 -It is a sad story that any company and developer would not want to experience. +这是一个任何公司和开发人员都不愿经历的悲惨故事。 -I won’t write more details about it. Instead, I hope more people know how to avoid it. Here are my suggestions for you to keep safe from Git leaks. +关于整件事情的细节我就不多写了。事实上,我希望更多的人知道如何去避免 Git 的信息泄露。以下是我提出的一些建议。 -## Build security awareness +## 建立安全意识 -Most junior developers don’t have enough security awareness. Some companies will train new employees, but some companies don’t have systematic training. +大多数新人开发者没有足够的安全意识。有些公司会培训新员工,但有些公司没有系统的培训。 -As a developer, we need to know which kind of data may introduce security issues. Remember these categories of data can not be checked into Git repository: +作为开发人员,我们需要知道哪些数据可能会带来安全问题。千万记住,下面这些数据不要上传到 Git 仓库中: -1. Any configuration data, including password, API keys, AWS keys, private keys, etc. -2. [Personally Identifiable Information](https://en.wikipedia.org/wiki/Personal_data) (PII). According to GDPR, if a company leaked the users’ PII, the company needs to notify users, relevant departments and there will be more legal troubles. +1. 任何配置数据,包括密码,API 密钥,AWS 密钥和私钥等。 +2. [个人身份信息](https://en.wikipedia.org/wiki/Personal_data)(PII)。根据 GDPR 的说法,如果公司泄露了用户的 PII,则该公司需要通知用户和有关部门,否则会带来更多的法律麻烦。 -If you are working for a company, don’t share any source code or data related to the company without permission. +如果你在公司工作,未经允许,请勿共享任何与公司相关的源代码或数据。 -Attackers can easily find some code with a company copyright on GitHub, which was accidentally leaked to Github by employees. +攻击者可以在 GitHub 上轻松地找到某些具有公司版权的代码,而这些代码都是被员工无意中泄露到 Github 上的。 -My advice is, try to distinguish between company affairs and personal stuff strictly. +我的建议是,应该将公司项目和个人项目严格区分。 -## Use Git ignore +## 使用 Git 忽略(Git ignore) -When we create a new project with Git, we must set a **.gitignore** properly. **gitignore** is a Git configuration file that lists the files or directories that will not be checked into the Git repository. +当我们使用 Git 创建一个新项目时,我们必须正确地设置一个 **.gitignore** 文件。**.gitignore** 是一个 Git 配置文件,它列出了不会被存入 Git 仓库的文件或目录。 -This project’s [gitignore](https://github.com/github/gitignore) is a collection of useful .gitignore templates, with all kinds of programming language, framework, tool or environment. +[这个 gitignore 项目](https://github.com/github/gitignore) 是一个实际使用着的 .gitignore 模板集合,其中包含对应各种编程语言、框架、工具或环境的配置文件。 -We need to know the pattern matching rules of **gitignore** and add our own rules based on the templates. +我们需要了解 **gitignore** 的模式匹配规则,并根据模板添加我们自己的规则。 ![](https://cdn-images-1.medium.com/max/2000/0*VmEolB6qYNCYr9Wf.png) -## Check commits with Git hooks and CI +## 使用 Git 钩子(Git hooks)和 CI 检查提交 -No tools could find out all the sensitive data from a Git repository, but a couple of tools and practices can help. +没有工具可以从 Git 仓库中找出所有敏感数据,但是有一些工具可以为我们提供帮助。 -[git-secrets](https://github.com/awslabs/git-secrets) and [talisman](https://github.com/thoughtworks/talisman) are similar tools, they are meant to be installed in local repositories as [pre-commit hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks). Every change will be checked before committed, pre-commit hooks will reject the commit if they detect a prospective commit may contain sensitive information. +[git-secrets](https://github.com/awslabs/git-secrets) 和 [talisman](https://github.com/thoughtworks/talisman) 是类似的工具,它们应作为[预提交的钩子](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks)(pre-commit hooks)安装在本地代码库中。每次都会在提交之前对更改的内容进行检查,如果钩子检测到预期的提交内容可能包含敏感信息,那它们将会拒绝提交。 -[gitleaks](https://github.com/zricethezav/gitleaks) provides another way to find unencrypted secrets and other unwanted data types in git repositories. We could integrate it into automation workflows such as CICD. +[gitleaks](https://github.com/zricethezav/gitleaks) 提供了另一种在 git 仓库中查找未加密的密钥和其他一些不需要的数据类型的方法。我们可以将其集成到自动化工作流程中,例如 CICD。 -## Code review +## 代码审查(Code review) -Code review is a best practice for team working. All the teammates will learn from each other’s source code. Junior developer’s code should be reviewed by developers with more experience. +代码审查是团队合作的最佳实践。所有队友都将从彼此的源代码中学习。初级开发人员的代码应由具有更多经验的开发人员进行审查。 -Most unintended changes can be found out during the code review stage. +在代码检查阶段可以发现大多数不符合预期的更改。 -[Enabling branch restrictions](https://help.github.com/en/github/administering-a-repository/enabling-branch-restrictions) can enforce branch restrictions so that only certain users can push to a protected branch in repositories. Gitlab has a similar option. +[启用分支限制](https://help.github.com/en/github/administering-a-repository/enabling-branch-restrictions) 可以强制执行分支限制,以便只有部分用户才能推送到代码库中受保护的分支。Gitlab 也有类似的选择。 -Setting master to a restricted branch helps us to enforce the code review workflow. +将 master 设置为受限制的分支有助于我们执行代码审查的工作。 ![](https://cdn-images-1.medium.com/max/2208/0*RUqDCQlDgym-Jo8x.png) -## Fix it quickly and correctly +## 快速并且正确地修复它 -With all the above tools and mechanisms, errors still could happen. If we fix it quickly and correctly, the leak may introduce no actual security issue. +即使使用了上面提到的工具和方法,却仍然可能会发生错误。但如果我们快速且正确地修复它,则代码泄漏可能就不会引起实际的安全问题。 -If we find some sensitive data leaked in the Git repository, we can not just make another commit to clean up. +如果我们在 Git 仓库中发现了一些敏感数据泄漏,我们就不能仅仅通过提交另一个提交覆盖的方式来进行清理。 ![This fix is self-deception](https://cdn-images-1.medium.com/max/2000/0*FsGBhHSlXdeSpTk4.png) -What we need to do is remove all the sensitive data from the entire Git history. +我们需要做的是从整个 Git 历史记录中删除所有敏感数据。 -**Remember to backup before any cleanup, and then remove the backup clone after we confirmed everything is OK**. +**在进行任何清理之前请记得进行备份,然后在确认一切正常后再删除备份文件。** -Use the `--mirror` to clone a bare repository; this is a full copy of the Git database. +使用 `--mirror` 参数克隆一个仓库;这是 Git 数据库的完整副本。 ```bash git clone --mirror git://example.com/need-clean-repo.git ``` -We need **git filter-branch** to remove data from all branches and commit histories. Suppose we want to remove `./config/passwd` from Git: +我们需要执行 **git filter-branch** 命令来从所有分支中删除数据并提交历史记录。 + +下面举个例子,假设我们要从 Git 中删除 `./config /passwd`: ```bash $ git filter-branch --force --index-filter \ @@ -90,7 +92,7 @@ $ git filter-branch --force --index-filter \ --prune-empty --tag-name-filter cat -- --all ``` -Remember to add the sensitive file to .gitignore: +请记住将敏感文件添加到 .gitignore 中: ```bash $ echo "./config/password" >> .gitignore @@ -98,32 +100,32 @@ $ git add .gitignore $ git commit -m "Add password to .gitignore" ``` -Then we push all branches to remote: +然后我们将所有分支推送到远端: ```bash $ git push --force --all $ git push --force --tags ``` -Tell our collaborators to rebase: +告诉我们的小伙伴进行变基(rebase): ```bash $ git rebase ``` -[BFG](https://rtyley.github.io/bfg-repo-cleaner/) is a faster and simpler alternative to **git filter-branch** for removing sensitive data. It’s usually 10–720x faster than **git filter-branch**. Except for deleting files, BFG could also be used to replace secrets in files. +[BFG](https://rtyley.github.io/bfg-repo-cleaner/) 是一种比 **git filter-branch** 更快、更简单的用于删除敏感数据的替代方法。通常比 **git filter-branch** 快 10–720 倍。除删除文件外,BFG 还可以用于替换文件中的机密信息。 -BFG will leave the latest commit untouched. It’s designed to protect us from making mistakes. We should explicitly delete the file, commit the deletion, then clean up the history to remove it. +BFG 保留最新的提交记录。它是用来防止我们犯错误的。我们应该显式地删除文件,提交删除,然后清除历史记录以此删除它。 -If the leaked Git repository is forked by others, we need to follow the [DMCA Takedown Policy](https://help.github.com/en/github/site-policy/dmca-takedown-policy#c-what-if-i-inadvertently-missed-the-window-to-make-changes) to ask Github to remove the forked repositories. +如果泄漏的 Git 代码库被其他人 fork 了,我们需要遵循 [DMCA](https://help.github.com/en/github/site-policy/dmca-takedown-policy#c-what-if-i-inadvertently-missed-the-window-to-make-changes) 的删除策略,请求 Github 删除创建的代码库。 -The whole procedure requires some time to finish, but it’s the only way to remove all the copies. +整个过程需要一些时间才能完成,但这是删除所有副本的唯一方法。 -## Conclusion +## 总结 -Don’t make the same mistake that countless people have made. Try to put some effort to avoid safety accidents. +不要犯无数人犯过的错误。尽力避免发生安全事故。 -Use these tools and strategies will help much in avoiding Git leaks. +使用上面提到的这些工具和策略将有助于避免 Git 泄漏。 > 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 diff --git a/TODO1/5-better-practices-for-javascript-promises-in-real-projects.md b/TODO1/5-better-practices-for-javascript-promises-in-real-projects.md new file mode 100644 index 00000000000..e1951105d24 --- /dev/null +++ b/TODO1/5-better-practices-for-javascript-promises-in-real-projects.md @@ -0,0 +1,406 @@ +> * 原文地址:[5 Better Practices for JavaScript Promises in Real Projects](https://medium.com/javascript-in-plain-english/5-better-practices-for-javascript-promises-in-real-projects-4917a9daec01) +> * 原文作者:[bitfish](https://medium.com/@bf2) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/5-better-practices-for-javascript-promises-in-real-projects.md](https://github.com/xitu/gold-miner/blob/master/TODO1/5-better-practices-for-javascript-promises-in-real-projects.md) +> * 译者: +> * 校对者: + +# 5 Better Practices for JavaScript Promises in Real Projects + +![Photo by [Kelly Sikkema](https://unsplash.com/@kellysikkema?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)](https://cdn-images-1.medium.com/max/10814/0*WrO6pqf5aLgB319V) + +After learning the basic usage of Promise, this article hopes to help you better use Promise in real projects. + +> Use Promise.all, Promise.race and Promise.prototype.then to improve your code quality. + +## Promise.all + +Promise.all is actually a promise that takes an array(or an iterable) of promises as an input. Then it gets resolved when all the promises get resolved or any one of them gets rejected. + +For example, assume that you have ten promises (Async operation to perform a network call or a database connection). You have to know when all the promises get resolved or you have to wait till all the promises resolve. So you are passing all ten promises to promise.all. Then, Promise.all itself as a promise will get resolved once all the ten promises get resolved or any of the ten promises get rejected with an error. + +**Let’s see it in code:** + +```js +Promise.all([promise1, promise2, promise3]) + .then(result) => { + console.log(result) + }) + .catch(error => console.log(`Error in promises ${error}`)) +``` + +As you can see, we are passing an array to promise.all. And when all three promises get resolved, promise.all resolves and the output is consoled. + +**Let’s see an example:** + +```JavaScript +// A simple promise that resolves after a given time +const timeOut = (t) => { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(`Completed in ${t}`) + }, t) + }) +} +// Resolving a normal promise. +timeOut(1000) + .then(result => console.log(result)) // Completed in 1000 +// Promise.all +Promise.all([timeOut(1000), timeOut(2000)]) + .then(result => console.log(result)) // ["Completed in 1000", "Completed in 2000"] +``` + +In the above example, Promise.all resolves after 2000 ms and the output is consoled as an array. + +One interesting thing about Promise.all is that the order of the promises is maintained. The first promise in the array will get resolved to the first element of the output array, the second promise will be a second element in the output array, and so on. + +OK, the above is the basic usage of promise.all. Let me introduce its application to the real project. + +#### 1. Synchronize multiple asynchronous requests + +In a real project, a page often needs to send multiple asynchronous requests to the background. And wait until the results in the background return before we start rendering the page. + +Some programmers may write code like this: + +```JavaScript +function getBannerList(){ + return new Promise((resolve,reject)=>{ + // Suppose we make an asynchronous request to the server + setTimeout(function(){ + resolve('BannerList') + },300) + }) +} + +function getStoreList(){ + return new Promise((resolve,reject)=>{ + // Suppose we make an asynchronous request to the server + setTimeout(function(){ + resolve('StoreList') + },500) + }) +} + +function getCategoryList(){ + return new Promise((resolve,reject)=>{ + // Suppose we make an asynchronous request to the server + setTimeout(function(){ + resolve('CategoryList') + },700) + }) +} + +getBannerList().then(function(data){ + // render data +}) +getStoreList().then(function(data){ + // render data +}) +getCategoryList().then(function(data){ + // render data +}) +``` + +The above code does work, but there are two defects in this code: + +* Each time we request data from the server, we need to write a separate function to process the data. This will lead to code redundancy and is not convenient for future upgrades and expansion. +* Each request takes a different amount of time, resulting in functions that render the page three times out of sync, and the user feels the page is stuck. + +Now we can use Promise.all to optimize our code. + +```JavaScript +function getBannerList(){ + // ... +} +function getStoreList(){ + // ... +} +function getCategoryList(){ + // ... +} + +function initLoad(){ + Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{ + // render datas + }).catch(err=>{ + // ... + }) +} +initLoad() +``` + +When all the requests are completed, we process the data uniformly. + +#### 2. Handle exceptions + +In the example above, we took this approach very directly to handling exceptions: + +```js +Promise.all([p1, p2]).then(res => { + // ... +}).catch(error => { + // handle error +}) +``` + +As we know, the Promise.all mechanism is that only if any promise instance in the promise array as a parameter throws an exception, then the entire Promise.all function will go directly into the catch method, regardless of whether other promise instances succeed or fail. + +But in practice, what we often need is this: even if one or more promise instances throw an exception, we still want Promise.all to continue executing normally. For example, in the above example, even if an exception occurs in `getBannerList()`, we continue to want to execute the program as long as no exception occurs in `getStoreList()` or `getCategoryList()`. + +To address this need, we can use a tip to enhance the Promise.all feature. We can write our code in this way: + +```JavaScript +Promise.all([p1.catch(error => error), p2.catch(error => error)]).then(res => { + // ... +})) +``` + +This way, even if an exception occurs in one promise instance, it does not interrupt other instances of Promise.all. + +Applied to the previous example, this is the result. + +```JavaScript +function getBannerList(){ + return new Promise((resolve,reject)=>{ + setTimeout(function(){ + // Suppose here reject an Error + reject(new Error('error')) + },300) + }) +} + +function getStoreList(){ + // ... +} + +function getCategoryList(){ + // ... +} + + +function initLoad(){ + Promise.all([ + getBannerList().catch(err=>err), + getStoreList().catch(err=>err), + getCategoryList().catch(err=>err) + ]).then(res=>{ + + if(res[0] instanceof Error){ + // handle error + } else { + // render data + } + + if(res[1] instanceof Error){ + // handle error + } else { + // render data + } + + if(res[2] instanceof Error){ + // handle error + } else { + // render data + } + }) +} + +initLoad() +``` + +#### 3. Let multiple promise instances work together + +When users try to upload or publish some content, we may need to verify the content provided by users. For example, check whether the content contains bloody violence, pornography, fake news, etc. In many cases, these detection behaviors are performed by different APIs provided by the backend or different cloud functions provided by SaaS service providers. + +Some programmers may write code like this: + +```JavaScript +function verify1(content){ + return new Promise((resolve,reject)=>{ + // Suppose we perform an asynchronous operation + setTimeout(function(){ + resolve(true) + },200) + }) +} + +function verify2(content){ + return new Promise((resolve,reject)=>{ + // Suppose we perform an asynchronous operation + setTimeout(function(){ + resolve(true) + },700) + }) +} + +function verify3(content){ + // Suppose we perform an asynchronous operation + return new Promise((resolve,reject)=>{ + setTimeout(function(){ + resolve(true) + },300) + }) +} + +verify1().then(() => { + verify2().then(() => { + verify3().then(() => { + // User content is approved and can be published. + }).catch(() => { + // User content is not approved and cannot be published. + }) + }).catch(() => { + // User content is not approved and cannot be published. + }) +}).catch(() => { + // User content is not approved and cannot be published. +}) +``` + +But with Promise.all, we can make different promise tasks work together: + +```JavaScript +function verify1(content){ + return new Promise((resolve,reject)=>{ + // Suppose we perform an asynchronous operation + setTimeout(function(){ + resolve(true) + },200) + }) +} + +function verify2(content){ + return new Promise((resolve,reject)=>{ + // Suppose we perform an asynchronous operation + setTimeout(function(){ + resolve(true) + },700) + }) +} + +function verify3(content){ + // Suppose we perform an asynchronous operation + return new Promise((resolve,reject)=>{ + setTimeout(function(){ + resolve(true) + },300) + }) +} + +let content = 'some content' +Promise.all([verify1(content),verify2(content),verify3(content)]).then(result=>{ + // User content is approved and can be published. +}).catch(err => { + // User content is not approved and cannot be published. +}) +``` + +## Promise.race + +The parameter of `promise.race` is the same as `promise.all`, which can be a promise array or an iterable object. + +The `Promise.race()` method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise. + +#### 4. Timing function + +When we request a resource asynchronously from the back-end server, we often limit a time. If no data is received within the specified time, an exception is thrown. + +Consider how you would implement this feature? Promise.race can help us solve this problem. + +```JavaScript +function requestImg(){ + var p = new Promise(function(resolve, reject){ + var img = new Image(); + img.onload = function(){ + resolve(img); + } + img.src = "https://www.example.com/a.png"; + }); + return p; +} + +// Delay function for timing requests +function timeout(){ + var p = new Promise(function(resolve, reject){ + setTimeout(function(){ + reject('Picture request timeout'); + }, 5000); + }); + return p; +} + +Promise +.race([requestImg(), timeout()]) +.then(function(results){ + // The resource request was completed within the specified time + console.log(results); +}) +.catch(function(reason){ + // The resource request did not complete within the specified time + console.log(reason); +}); + +``` + +## Promise.then + +We know that `promise.then()` always returns a promise object, so `promise.then` supports chain calls. + +```js +Promise.then().then().then() +``` + +#### 5. Promise Chaining + +Therefore, if the amount of data returned by the interface is large and the processing in one then seems bloated, we can consider interviewing the processing logic and executing it in turns in multiple then methods: + +```JavaScript +// Suppose this is the data returned by the backend +let result = { + bannerList:[ + //... + ], + storeList:[ + //... + ], + categoryList:[ + //... + ], + //... +} + +function getInfo(){ + return new Promise((resolve,reject)=>{ + setTimeout(()=>{ + resolve(result) + },500) + }) +} + +getInfo().then(res=>{ + + let { bannerList } = res + + // do something with bannerList + console.log(bannerList) + + // Return res for the next then method + return res + +}).then(res=>{ + let { storeList } = res + console.log(storeList) + return res + +}).then(res=>{ + let { categoryList } = res + console.log(categoryList) + return res +}) +``` + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[区块链](https://github.com/xitu/gold-miner#区块链)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/5-ways-to-create-a-settings-icon.md b/TODO1/5-ways-to-create-a-settings-icon.md index cc2ac295d90..2a62d6a4e6a 100644 --- a/TODO1/5-ways-to-create-a-settings-icon.md +++ b/TODO1/5-ways-to-create-a-settings-icon.md @@ -2,276 +2,276 @@ > * 原文作者:[Helena Zhang](https://medium.com/@minoraxis) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/5-ways-to-create-a-settings-icon.md](https://github.com/xitu/gold-miner/blob/master/TODO1/5-ways-to-create-a-settings-icon.md) -> * 译者: -> * 校对者: +> * 译者:[Jessica](https://github.com/cyz980908) +> * 校对者:[QinRoc](https://github.com/QinRoc) -# 5 Ways to Create a Settings Icon +# 创建一个 Settings icon 的五种方法 -> Learn to use an array of Illustrator features through this exercise +> 通过这篇文章我们可以学习如何使用 Illustrator 的一系列功能 ![](https://cdn-images-1.medium.com/max/4000/1*KXjeemJInI5xQg7HpC2Z9g.png) -The gear has become a ubiquitous symbol for **settings** in our digital applications. +齿轮图标已经成为了**设置**符号的标配。 -![Left to right, top to bottom: Google Calendar, Lyft, Dribbble, Facebook, ClassPass, Seamless, Telegram, Reddit, Duolingo, Dropbox, Instagram, Headspace, PayPal, Transit, WeChat](https://cdn-images-1.medium.com/max/4000/1*wu3FKUzPxQhjf4Wk_uCJIw.png) +![自左向右,从上到下分别是:Google Calendar、Lyft、Dribbble、Facebook、ClassPass、Seamless、Telegram、Reddit、Duolingo、Dropbox、Instagram、Headspace、PayPal、Transit、WeChat](https://cdn-images-1.medium.com/max/4000/1*wu3FKUzPxQhjf4Wk_uCJIw.png) -There are many interesting ways you can create this icon. We’ll walk though 5 of them in Adobe Illustrator, to pick up techniques you can take forward to any vector drawing. +有许多有趣的方法可以实现这个图标。我们将学习使用 Adobe Illustrator 实现的 5 个方法,同时还可以学习到适用于任何矢量绘图的技术。 -(**Keyboard shortcuts** are shown in parentheses.) +(**键盘快捷键**备注在括号中。) -## Method 1: Rounded Star +## 方法 1:圆角星 -This simple method is effective for a gear with pointed teeth. +这种简单的方法能很方便地生成带有尖齿的齿轮。 ![](https://cdn-images-1.medium.com/max/3200/1*nUCNYG_c9hNEnMm66rd2zQ.gif) -Select the **Star Tool** and **click** anywhere on your canvas. +选择**星型工具**,然后**点击**画布上的任意地方。 ![](https://cdn-images-1.medium.com/max/4002/1*qR6U11bjhUffWP7yLvFf9Q.png) -Play with the parameters. +填入参数。 ![](https://cdn-images-1.medium.com/max/4000/1*gQtrRMFzcjAEudBS1SdQnw.png) -Round the corners—with the **Direct Selection Tool** (**A**) selected, hover over the shape. **Drag** one of the little circular handles to modify all corners. **Double click** a handle to specify a precise corner radius. +用**直接选择工具**(**A**)选中圆角,将鼠标悬停在图标上。**拖动**其中的一个小圆形手柄来修改所有的圆角。**双击**手柄可以来定精确的角半径。 ![](https://cdn-images-1.medium.com/max/4000/1*KgDXl0u5M1WbrAn5MHPWzQ.png) -Draw a smaller concentric circle with the **Ellipse Tool** (**L**) to create the eye. +用**椭圆工具**(**L**)画一个小一些的同心圆来创建中间的小洞。 ![](https://cdn-images-1.medium.com/max/4000/1*tmhumNqUD265crJuT7wsCQ.png) -You can draw the circle free-form or **L** + **click** anywhere on the canvas to specify the exact width and height. +你可以绘制任意大小的圆圈,或者在画布上的任意位置按**L** + **单击**以指定确切的宽度和高度。 ![](https://cdn-images-1.medium.com/max/4000/1*o4LZ-p6BMnXDuuHoth-4sw.png) -Use the **Transform** panel to make any adjustments to the dimensions. +可以在“**变换(Transform)**”面板对尺寸进行调整。 ![](https://cdn-images-1.medium.com/max/4000/1*taP3_LzZS0jakCB8K2Cg3w.png) -With both shapes selected, clean up the icon by subtracting the smaller circle from the rounded star (**Pathfinder** panel > **Minus Front**). +在选中两个图案后,通过从圆角星上减去较小的圆来清理图标(**路径查找器(Pathfinder)** 面板 > **减去前面的图案(Minus Front)**)。 ![](https://cdn-images-1.medium.com/max/4000/1*OXarwXairILpFXYEXZ-6IA.png) -Voilà. +瞧!完成了! ![](https://cdn-images-1.medium.com/max/4000/1*tmhumNqUD265crJuT7wsCQ.png) -## Method 2: Zig Zag +## 方法 2:锯齿形 -Let’s try something different to get to a similar effect. +让我们尝试一些不同的方法来达到类似的效果。 ![](https://cdn-images-1.medium.com/max/3200/1*2tdaBuJsgIuzQgoX2i7EpQ.gif) -Draw a circle (**L**) with a fill, no stroke. +绘制一个有填充(fill),没描边(stroke)的圆(**L**)。 ![](https://cdn-images-1.medium.com/max/4000/1*0SnYSS0olXodPNhr4LjCPQ.png) -Select the circle and apply a **Zig Zag Effect** (**Effect** > **Distort & Transform** > **Zig Zag**). +选择圆并应用**锯齿形效果**(**特效(Effect)** > **扭曲 & 变形(Distort & Transform)** > **锯齿形(Zig Zag)**)。 ![](https://cdn-images-1.medium.com/max/4000/1*iHGSBfduKeAKL8BB8ScORw.png) -Play with the parameters with Preview toggled on. +在预览选项打开的情况下设置参数。 ![](https://cdn-images-1.medium.com/max/4000/1*IpiYKyL7OyyznM4QcVfCwQ.png) -Now we’ll try a smaller eye. Draw a concentric circle. +现在我们要试着画一个小一些的同心圆来创建中间的小洞。 ![](https://cdn-images-1.medium.com/max/4000/1*Dvsus1_nX3QpU7E3Db8Akg.png) -Select both shapes. Because we have used an effect, we’ll have to expand the appearance before we merge shapes. Go to **Object** > **Expand Appearance**. +选择这两个形状。因为我们已经使用了一些特效,所以在合并形状之前必须扩充外观。点击**对象(Object)** > **扩展外观(Expand Appearance)**。 -**Why? Effects are dynamic and non-destructive, which means you can go back and change the parameters at any time. Because of this, effects need to be expanded before performing further shape manipulation.** +**为什么要这样做?因为特效是可回退的、不会被破坏的,这意味着你可以随时返回并更改参数。因此,在对图形执行进一步的操作之前,需要对特效进行扩展。** ![](https://cdn-images-1.medium.com/max/4000/1*TpvBycrleLcrPgQhobWR2g.png) -Similar to Method 1, we’ll clean up the icon by subtracting the smaller circle from the larger shape (**Pathfinder** panel > **Minus Front**). +与方法 1 类似,我们将通过从较大的形状中减去较小的圆来得到图标(**路径查找器(Pathfinder)** 面板 > **减去前面(Minus Front)**)。 ![](https://cdn-images-1.medium.com/max/4000/1*Dvsus1_nX3QpU7E3Db8Akg.png) -## Method 3: Additive Rotation +## 方法 3:加法和旋转 -Here’s a more complex method that’ll allow us more customization in the gear teeth. We’ll go for a sharper look this time. +这是一个相对来说更复杂的方法,它允许我们对齿轮进行更多的定制。这次我们要看得更仔细一些。 ![](https://cdn-images-1.medium.com/max/3200/1*GzTAZ69HhqNh9bRO_iaifQ.gif) -Draw a circle with a fill, no stroke. +绘制一个有填充(fill),没描边(stroke)的圆。 ![](https://cdn-images-1.medium.com/max/4000/1*JYSKqVx1RvG33CGiDOKtbg.png) -Draw a rectangle with the **Rectangle Tool** (**M**) on top, centered to the circle. +使用在顶部的**矩形工具**(**M**),以圆心为中心绘制一个矩形。 ![](https://cdn-images-1.medium.com/max/4000/1*WZZJpZZOQakA5P9-tmCWhw.png) -'Bulge’ the rectangle. There are many ways to do this. You can select the rectangle and use the **Bulge Effect** (**Effect** > **Warp** > **Bulge**). +“凸出”矩形。有很多方法可以做到这一点。你可以选择矩形并使用**凸出特效(Bulge Effect)** (**特效(Effect)** > **变形(Warp)** > **凸出(Bulge)**)。 ![](https://cdn-images-1.medium.com/max/4000/1*BrNsh1yHIYKrDaMqbYD4iQ.png) -My preferred method is to add anchor points and use the **Direct Selection Tool** (**A**) to select specific ones to manipulate. +但我的首选方法是添加锚点,并使用**直接选择工具(Direct Selection Tool)**(**A**)来选择要操作的特定锚点。 ![](https://cdn-images-1.medium.com/max/4000/1*B1LwQLNXeZwpBAS6qSiE5w.png) -To add additional anchor points that are of equal distance from the current ones, select an object and use **Object** > **Path** > **Add Anchor Points**. You can also use the **Pen Tool** (**P**) to manually add points. +若要添加与当前锚点距离相等的其他锚点,请选择一个对象并使用**对象(Object)** > **路径(Path)** > **添加锚点(Add Anchor Points)**。你也可以使用**钢笔工具**(**P**)手动添加点。 ![](https://cdn-images-1.medium.com/max/4000/1*8ipZwTSEe0OYrNY1Ux21zA.png) -With your shape selected, press **R** for the **Rotate Tool** then **option** + **click** the center of the circle to set that as the reference point. The **Rotate** panel will come up. +选定形状后,按**R**键选择**旋转工具(Rotate Tool)**,然后按**option**键 + **单击**圆心将其设置为参考点。**旋转**面板就会出现。 ![](https://cdn-images-1.medium.com/max/4000/1*ogbTsoIyKr2kYnQjKBe71g.png) -Choose an angle. A 45° angle will create a gear with 8 teeth (360° divided by 8 is 45°). +选择一个角度。45° 角会创造出有 8 个齿的齿轮(360° 除以 8 等于 45°)。 -Here’s the fun part. +接下来有趣的事情发生了。 -Press **Copy** (**not** **OK**). This will copy your shape with the angle and reference point you specified. +点击 **复制(Copy)** (**注意不是点击** **OK**)。这将会按照你设定的角度和参考点复制你的图案。 ![](https://cdn-images-1.medium.com/max/4000/1*CaoipHnbmfFitWDLsSlO_A.png) -Repeat the action by pressing **Command** + **D** (macOS) or **Ctrl + D** (Windows). Do this twice to complete the circle. +通过按 **Command** + **D**(macOS)或 **Ctrl** + **D**(Windows)的重复操作,这样做两次后,就可以完成这个圆。 ![](https://cdn-images-1.medium.com/max/4000/1*4IyH9hCk6usy8IAmJhuTSA.png) -Alternatively, you can use the **Transform** **Effect** (**Effect** > **Distort & Transform** > **Transform**) to achieve the same rotational copies. +或者,你可以使用**变换特效**(**特效(Effect)** > **扭曲 & 变形(Distort & Transform)** > **变换(Transform)** 来实现同样的旋转拷贝。 ![](https://cdn-images-1.medium.com/max/4000/1*8JoVfMIcd7fUc4A72VS1PQ.png) -Effects are non-destructive; whenever you apply one, you can edit it in the **Properties** panel. +刚刚我们说到,特效(Effect)是不会被破坏的,所以每当你用了一个特效图案,你就可以在**属性**面板中编辑它。 ![](https://cdn-images-1.medium.com/max/4000/1*kfB214QfGr1BEvDu6Pg5vQ.png) -Time to clean up our shapes and add the inner circle. +接着我们给图案添加内圆。 -Combine all shapes with **Pathfinder** panel > **Unite**. +点击**路径查找器(Pathfinder)**面板组合 > 合并**(Unite)**,来将所有图形合并。 ![](https://cdn-images-1.medium.com/max/4000/1*BJVbsvvgcKtWpKjAH3AEmw.png) -Draw a smaller concentric circle. +画一个小一点的同心圆。 ![](https://cdn-images-1.medium.com/max/4000/1*Vo4vsPYqBJVfb-IinHiwFw.png) -Use **Pathfinder** panel > **Minus Front** to subtract the smaller circle from the larger shape. +点击 **路径查找器(Pathfinder)** 面板 > **减去前面的图案(Minus Front)** 来从较大的形状中减去中心较小的圆。 -Experiment! Different source shapes yield different end results. +不断尝试!不同的源图形会产生不同的齿轮结果。 ![](https://cdn-images-1.medium.com/max/4000/1*ZBAsEntImNjJ4m_GuoMlbw.png) -## Method 4: Subtractive Rotation +## 方法 4:减法和旋转 -Method 4 is similar to Method 3. +方法 4 与方法 3 类似。 ![](https://cdn-images-1.medium.com/max/3200/1*cujGoMTZXBrhMfm4WoLMaA.gif) -Draw a circle with a fill, no stroke. +绘制一个有填充(fill),没描边(stroke)的圆。 ![](https://cdn-images-1.medium.com/max/4000/1*wpX-ymZRcOULMhAEcltU6A.png) -Draw a small circle aligned to the top. +画一个与顶部对齐的小圆。 ![](https://cdn-images-1.medium.com/max/4000/1*Gd3D7WDFPhMBAjY6R4il5Q.png) -Select the small circle, press **R** for **Rotate**, then **option** + **click** the center of the circle. Let’s try 6 teeth this time (360°/6). Illustrator will do the calculation for you if you type “360/6” directly. +选择这个小圆,按 **R** 键进行**旋转**,接着按下 **option** 并同时**点击**圆心。这次我们尝试一下 6 颗齿(360°/ 6)的齿轮。如果直接输入 “360/6”,则 Illustrator 将会自动为你计算。 ![](https://cdn-images-1.medium.com/max/4000/1*vRGHB4sIUoU43OuFBOv1ZQ.png) -Press **Copy**. +点击复制(**Copy**)。 ![](https://cdn-images-1.medium.com/max/4000/1*xkpTHE3W9Y4JcYZ_aV1fyg.png) -Repeat the action by pressing **Command** + **D** (macOS) or **Ctrl + D** (Windows) 4 times. +通过按 **Command** + **D**(macOS)或 **Ctrl** + **D**(Windows)4 次重复该操作。 ![](https://cdn-images-1.medium.com/max/4000/1*hGWMbewTlFDoxs_B--RICw.png) -Use **Pathfinder** panel > **Minus Front** to subtract the small circles from the big circle. +使用**路径查找器(Pathfinder)** 面板 > **减去前面的图案(Minus Front)**,从大的圆中减去小的圆。 -Let’s round the corners. With the **Direct Selection Tool** (**A**), **click** + **drag** the little dots to adjust the corner radiuses. +接着我们给拐角加一些圆角。使用**直接选择工具(Direct Selection Tool)**(**A**),拖动小圆点来调整圆角的半径。 ![](https://cdn-images-1.medium.com/max/4000/1*ZmT0d39tbi01GCVK9lV1Gg.png) -Draw a smaller concentric circle for the eye and subtract the smaller circle from the larger shape. +画一个小一些的同心圆来创建中间的小洞,从较大的图形中减去中心的小圆。 ![](https://cdn-images-1.medium.com/max/4000/1*Gtie3FaqFE4zPLpSUZLyiA.png) -A few more ideas (try different shapes): +可以尝试更多的创意(尝试不同的形状): ![](https://cdn-images-1.medium.com/max/4000/1*KThz-U48zcTbttXMSAqE8Q.png) -## Method 5: Intersect +## 方法 5:交叉贯穿 -For the last method we’ll bring back the **Star Tool**. +最后一种方法又会用到**星型工具(Star Tool)**。 ![](https://cdn-images-1.medium.com/max/3200/1*06OYxdrlLjWHY1_7szWRSQ.gif) -Draw a star. +画一个星形。 ![](https://cdn-images-1.medium.com/max/4000/1*ptztuAZB-wBoq9DjIpnHtg.png) -Draw a concentric circle on top. +在上面画一个同心圆。 ![](https://cdn-images-1.medium.com/max/4000/1*Zqu7DDvt0TVfV2rmlYqJLQ.png) -Select both shapes. **Pathfinder** panel > **Intersect**. +选择这两个图形。**路径查找器(Pathfinder)** 面板 > **相交(Intersect)**。 ![](https://cdn-images-1.medium.com/max/4000/1*GvWwEYCuXHmZ7lzpp0nZnQ.png) -Draw another concentric circle on top like so: +再如下图所示,在上面画一个同心圆: ![](https://cdn-images-1.medium.com/max/4000/1*NclTXoSkV7L1ESDlALrGAg.png) -**Pathfinder** panel > **Unite**. Now we have a silhouette of a gear. +**路径查找器(Pathfinder)** 面板 > **联合(Unite)**。 现在我们得到一个齿轮的轮廓啦。 ![](https://cdn-images-1.medium.com/max/4000/1*9kHuhbQmdzgV6HPRi07jAA.png) -You know what to do — draw a third concentric circle and subtract the smaller circle from the larger shape. +接下来怎么做你应该都已经知道了吧 —— 绘制第三个同心圆,然后从较大的形状中减去较小的圆。 ![](https://cdn-images-1.medium.com/max/4000/1*5f7m7XfibpX337PGdK2k4A.png) -More with this method: +用此方法还可以创造出更多花样: ![](https://cdn-images-1.medium.com/max/4000/1*Sg4yaJDl5jo2CDaNe5BMUw.png) -## Experiment to Find Your Flow +## 尝试找到自己的方法 -Hope you learned a trick or two from this exercise. Similar methods may be applied in UI-focused vector software like Sketch or Figma, though Illustrator is more precise. +希望你能从这个教程中学到一两招。虽然 Illustrator 绘图更精密一些,但文中类似的方法也可以应用于一些 UI 制作的矢量软件中,如 Sketch 或 Figma。 -From here, explore different icon styles. +从这里,探索些不同的图标风格。 ![](https://cdn-images-1.medium.com/max/4000/1*L3xu6HufPN2eJMFwNwWQMQ.png) -## Bonus +## 彩蛋 -Some more food for thought… +更多给你带来灵感的创意。 -#### 2 Squares = 1 Star +#### 2 个正方形 = 1 个星 -You can create an 8-pointed star by drawing 2 squares. **Shift** + **drag** with the **Rectangle Tool** (**M**) to create a square, select and **shift** + **drag** with the **Rotate Tool** (**R**) to rotate in 45° increments. +你可以通过画两个正方形来创建一个八角星。按下 **Shift** 键的同时,拖动 **矩形工具(M)** 创建一个正方形,选中这个正方形,按下 **Shift** 键的同时,**拖动旋转工具(R)** 来旋转 45°。 ![](https://cdn-images-1.medium.com/max/4000/1*4KOU3XYdLAiaWe33ipWsNg.png) -#### Roundabout Rounding +#### 环形圆角 -Once upon a time, I may have hacked rounded corners by adding a stroke — overcomplicated and imprecise. Oof. +在以前,我可能会通过添加笔触(stroke)来完成圆角,现在看来但这实在是一个复杂又不精确的方法。(真是令人尴尬) ![](https://cdn-images-1.medium.com/max/4000/1*l11n93ZXg-wrMYtKyeh-nQ.png) -#### Scribble to Shape +#### 涂鸦的造型 -If you’re using a tablet or touch interface, you can use the wacky **Shaper Tool** (**shift** + **N**) to non-destructively combine or subtract shapes. Scribble over like so to ‘delete’ the desired areas. The original shapes will be preserved. +如果你使用的是平板电脑或者是触控板,你可以使用古怪的 **Shaper 工具**(**Shift** + **N**)来对图形进行不会被破坏的组合或删减。就像下面这样乱涂乱画来“删除”想要的区域。最后原始的图形会被保留下来。 ![](https://cdn-images-1.medium.com/max/4000/1*O-W5KyVHqmUgO4TWnvO0nA.png) --- -🎶 **Written to the sounds of: [Mogwai](https://open.spotify.com/artist/34UhPkLbtFKRq3nmfFgejG?si=QsV-S2PuTlKKJTzlRF1uDw)** +🎶 **文章的音频版:[Mogwai](https://open.spotify.com/artist/34UhPkLbtFKRq3nmfFgejG?si=QsV-S2PuTlKKJTzlRF1uDw)** -🙏 **Thanks to: Toby Fried, Tate Chow, Christine Lee, Pawel Piekarski, and Monica Chang** +🙏 **感谢:Toby Fried、Tate Chow、Christine Lee、Pawel Piekarski、Monica Chang** --- -More about iconography: +更多关于图像设计的文章: -* [7 Principles of Icon Design](https://medium.com/@minoraxis/7-principles-of-icon-design-e7187539e4a2) -* Icon Grids & Keylines Demystified **(coming soon)** -* Pixel-Snapping in Icon Design: To Snap or Not to Snap **(coming soon)** +* [关于 icon 设计的 7 个准则](https://medium.com/@minoraxis/7-principles-of-icon-design-e7187539e4a2) ([译文连接](https://juejin.im/post/5e5dbd3e6fb9a07cd323dd2b)) +* icon 网格 & 关键线大揭秘 **(即将推出)** +* icon 设计中的像素捕捉:捕捉或不捕捉 **(即将推出)** > 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 diff --git a/TODO1/custom-encoding-and-decoding-json-in-swift.md b/TODO1/custom-encoding-and-decoding-json-in-swift.md new file mode 100644 index 00000000000..06a3d11719b --- /dev/null +++ b/TODO1/custom-encoding-and-decoding-json-in-swift.md @@ -0,0 +1,305 @@ +> * 原文地址:[Custom encoding and decoding JSON in Swift](https://levelup.gitconnected.com/custom-encoding-and-decoding-json-in-swift-a99c80b280e7) +> * 原文作者:[Leandro Fournier](https://medium.com/@lean4nier) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/custom-encoding-and-decoding-json-in-swift.md](https://github.com/xitu/gold-miner/blob/master/TODO1/custom-encoding-and-decoding-json-in-swift.md) +> * 译者: +> * 校对者: + +# Custom encoding and decoding JSON in Swift + +![](https://cdn-images-1.medium.com/max/2000/0*-t2P3atrbgHMKR6P.jpg) + +> This post was originally written by me at [Swift Delivery](https://www.leandrofournier.com/custom-encoding-and-decoding-json/). + +In our last [Working with JSON in Swift series](https://levelup.gitconnected.com/working-with-json-in-swift-c5faea0b19a1), we explore various items: + +* `Codable` protocol, which contains two other protocols: `Encodable` and `Decodable` +* How to decode a JSON data object into a readable Swift struct +* Usage of custom keys +* Custom objects creation +* Arrays +* Different top level entities + +That’s enough for a basic usage of JSON in Swift, which will enable us to read JSON data (decode) and create a new object which can be converted back to JSON (encode) and send it, for instance, to a RESTFul API. + +So, first things first: let’s create an object and convert it to a JSON data format. + +## Encoding + +#### Default encoding + +Let’s assume we have the following `struct` for insects: + +```swift +struct Insect: Codable { + let insectId: Int + let name: String + let isHelpful: Bool + + enum CodingKeys: String, CodingKey { + case insectId = "insect_id" + case name + case isHelpful = "is_helpful" + } +} +``` + +To sum up, we have three properties. **insectId** for the insect identifier, **name** for its name and **isHelpful** to specify if the insect is helpful or not to our garden. Two of these properties use custom keys (**insectId** and **isHelpful**). + +Now let’s create an insect: + +```swift +let newInsect = Insect(insectId: 1006, name: "ants", isHelpful: true) +``` + +Our RESTful API expects to receive a JSON with this new insect information. Then we have to encode it: + +```swift +let encoder = JSONEncoder() +let insectData: Data? = try? encoder.encode(newInsect) +``` + +That was easy: now **insectData** is an object of type `Data?`. We might want to check whether the encoding actually worked (just a check, you probably won't do this in your code). Let's rewrite the code above and use some optional unwrapping: + +```swift +let encoder = JSONEncoder() +if let insectData = try? encoder.encode(newInsect), + let jsonString = String(data: insectData, encoding: .utf8) + { + print(jsonString) +} +``` + +1. Create the encoder +2. Try to encode the object we’ve created +3. Convert, if possible, the `Data` object into a `String` + +Then we print out the result which will look like this: + +```json +{"name":"ants","is_helpful":true,"insect_id":1006} +``` + +> Note that the keys used while encoding are not the custom keys (**insectId** and **isHelpful**) but the expected keys (**insect_id** and **is_helpful**). Nice! + +#### Custom encoding + +Let’s suppose our RESTful API expects to receive the name of the insect uppercased. We need to create our own implementation of the encoding method so to make sure the name of the insect is sent uppercased. We must implement the method **func** encode(to encoder: Encoder) **throws** of the `Encodable` protocol inside our **Insect** `struct`. + +```swift +struct Insect: Codable { + let insectId: Int + let name: String + let isHelpful: Bool + + enum CodingKeys: String, CodingKey { + case insectId = "insect_id" + case name + case isHelpful = "is_helpful" + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(insectId, forKey: .insectId) + try container.encode(name.uppercased(), forKey: .name) + try container.encode(isHelpful, forKey: .isHelpful) + } +} +``` + +Line 13 is where we create a container where encoded values will be stored. The container MUST be a `var` because it's mutable and MUST receive the keys to be used. + +Lines 14 to 16 are used to encode the values into the container, which is done using `try` because any of these can throw an error. + +Now, look at line 15: we don’t just put the value as it is but we uppercase it, which is main reason we’re implementing a custom encoding. + +If you run the code above, where we create the **Insect** “ants”, we’ll see that after encoding and converting the resulting JSON `Data` into a `String` we get the following: + +``` +{"name":"ANTS","is_helpful":true,"insect_id":1006} +``` + +As you might have seen, the name of the insect is now uppercased despite we’ve created it lowercased. How cool is that! + +## Custom decoding + +So far we’ve been relying on the default decoding method of the `Decodable` protocol. But let's take a look at another scenario. + +```json +[ + { + "insect_id":1001, + "name":"BEES", + "details":{ + "is_helpful":true + } + }, + { + "insect_id":1002, + "name":"LADYBUGS", + "details":{ + "is_helpful":true + } + }, + { + "insect_id":1003, + "name":"SPIDERS", + "details":{ + "is_helpful":true + } + }, + { + "insect_id":2001, + "name":"TOMATO HORN WORMS", + "details":{ + "is_helpful":false + } + }, + { + "insect_id":2002, + "name":"CABBAGE WORMS", + "details":{ + "is_helpful":false + } + }, + { + "insect_id":2003, + "name":"CABBAGE MOTHS", + "details":{ + "is_helpful":false + } + } +] +``` + +The API is retrieving the **is_helpful** property inside a **details** entity. But we don’t want to create a **Details** object: we just want to flatten that object so we can use our existing **Insect** object. + +Time to use our own implementation of the **init**(from decoder: Decoder) **throws** method of the `Decodable` protocol and some extra work. Let's get started. + +First of all, coding keys has changed because **is_helpful** is not a key in the same level as before AND there’s a new one called **details**. To fix that: + +```swift +enum CodingKeys: String, CodingKey { + case insectId = "insect_id" + case name + case details + } + + enum DetailsCodingKeys: String, CodingKey { + case isHelpful = "is_helpful" + } +``` + +In line 4 we replace the existing key with the new one, **details**. + +In lines 7 and 9 we create a new set of keys, the ones that exist inside **details**, which in this case is just one, **isHelpful**. + +> Note that we didn’t touch the properties of the **Insect** `struct`. + +Now let’s dive into the decoder initialization: + +```swift +init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + insectId = try container.decode(Int.self, forKey: .insectId) + name = try container.decode(String.self, forKey: .name) + let details = try container.nestedContainer(keyedBy: DetailsCodingKeys.self, forKey: .details) + isHelpful = try details.decode(Bool.self, forKey: .isHelpful) +} +``` + +In line 2 we create the container which decodes the whole JSON structure. + +In line 4 and 5 we just decode the `Int` value for the **insectId** property and the `String` value for the **name** property. + +In line 6 we grab the nested container under the **details** key which is keyed by the brand new **DetailsCodingKeys** `enum`. + +In line 7 we just decode the `Bool` value for the **isHelpful** property inside our new **details** container. + +BUT that’s not it. Since our **CodingKeys** has changed by adding the **details** case, our custom encoding implementation must be fixed. So let’s do that: + +```swift +func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(insectId, forKey: .insectId) + try container.encode(name.uppercased(), forKey: .name) + var details = container.nestedContainer(keyedBy: DetailsCodingKeys.self, forKey: .details) + try details.encode(isHelpful, forKey: .isHelpful) +} +``` + +We just changed the way we encode the **isHelpful** property. + +In line 5 we create a new nested container, using the keys inside the **DetailsCodingKeys** `enum` and to be used inside the **details** entity inside our JSON. + +In line 6 we encode **isHelpful** INSIDE the brand new **details** nested container. + +So, to sum up, our **Insect** `struct` looks like this: + +```swift +struct Insect: Codable { + let insectId: Int + let name: String + let isHelpful: Bool + + enum CodingKeys: String, CodingKey { + case insectId = "insect_id" + case name + case details + } + + enum DetailsCodingKeys: String, CodingKey { + case isHelpful = "is_helpful" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + insectId = try container.decode(Int.self, forKey: .insectId) + name = try container.decode(String.self, forKey: .name) + let details = try container.nestedContainer(keyedBy: DetailsCodingKeys.self, forKey: .details) + isHelpful = try details.decode(Bool.self, forKey: .isHelpful) + + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(insectId, forKey: .insectId) + try container.encode(name.uppercased(), forKey: .name) + var details = container.nestedContainer(keyedBy: DetailsCodingKeys.self, forKey: .details) + try details.encode(isHelpful, forKey: .isHelpful) + } +} +``` + +If we decode it: + +```swift +let decoder = JSONDecoder() +if let insects = try? decoder.decode([Insect].self, from: jsonData!) { + print(insects) +} +``` + +We’ll get something like this: + +``` +[__lldb_expr_54.Insect(insectId: 1001, name: "BEES", isHelpful: true), __lldb_expr_54.Insect(insectId: 1002, name: "LADYBUGS", isHelpful: true), __lldb_expr_54.Insect(insectId: 1003, name: "SPIDERS", isHelpful: true), __lldb_expr_54.Insect(insectId: 2001, name: "TOMATO HORN WORMS", isHelpful: false), __lldb_expr_54.Insect(insectId: 2002, name: "CABBAGE WORMS", isHelpful: false), __lldb_expr_54.Insect(insectId: 2003, name: "CABBAGE MOTHS", isHelpful: false)] +``` + +As you can see, there’s no **details** entity: just our `struct` properties. + +The encoding will work as expected as well. + +This post, plus the previous series, sums up pretty much the most common scenarios you may run into when working with JSON in Swift. + +## Need more information? + +Ben Scheirman’s [Ultimate Guide to JSON Parsing with Swift](https://benscheirman.com/2017/06/swift-json/) is the most useful source I could find for this subject. + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[区块链](https://github.com/xitu/gold-miner#区块链)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/fast-pipelines-with-generators-in-typescript.md b/TODO1/fast-pipelines-with-generators-in-typescript.md index 513ae294fed..68e447d7b79 100644 --- a/TODO1/fast-pipelines-with-generators-in-typescript.md +++ b/TODO1/fast-pipelines-with-generators-in-typescript.md @@ -2,14 +2,14 @@ > * 原文作者:[Wim Jongeneel](https://medium.com/@wim.jongeneel1) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/fast-pipelines-with-generators-in-typescript.md](https://github.com/xitu/gold-miner/blob/master/TODO1/fast-pipelines-with-generators-in-typescript.md) -> * 译者: -> * 校对者: +> * 译者:[febrainqu](https://github.com/febrainqu) +> * 校对者:[xionglong58](https://github.com/xionglong58),[GJXAIOU](https://github.com/GJXAIOU),[lsvih](https://github.com/lsvih) -# Lazy Pipelines with Generators in TypeScript +# TypeScript 中带生成器的惰性管道 ![Photo by [Quinten de Graaf](https://unsplash.com/@quinten149?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)](https://cdn-images-1.medium.com/max/9704/1*wEQnHaPoHc_QJo5vxwrCEg.jpeg) -In recent years the JavaScript community has embraced the functional array methods like `map` and `filter`. Writing for-loops has become something that gets associated with 2015 and JQuery. But the array methods in JavaScript are far from ideal when we are talking about performance. Lets look at an example to clarify the issues: +近年来,JavaScript 社区已经接受了 `map` 和 `filter` 之类的函数式数组方法,for 循环成为了只能在 Jquery 中见到的东西。但在性能方面,JavaScript 中的数组方法还远远达不到预期。让我们看一个例子: ```TypeScript const x = [1,2,3,4,5] @@ -18,24 +18,24 @@ const x = [1,2,3,4,5] [0] ``` -This code will execute the following steps: +这段代码将执行以下步骤: -* create the array with 5 items -* create a new array with all the numbers doubled -* create a new array with the numbers filtered -* take the first item +* 创建一个含有五个元素的数组 +* 创建一个新数组,其元素值是前一个数组对应元素的 2 倍 +* 创建一个符合过滤条件的新数组 +* 取数组的第一个元素 -This involves a lot more stuff happening then is actually needed. The only thing has to happen is that the first item that passes `x > 5` gets processed and returned. In other languages (like Python) iterators are used to solve this issue. Those iterators are a lazy collection and only processes data when it is requested. If JavaScript would use lazy iterators for its array methods the following would happen instead: +实际上有很步骤是多余的,上述代码做的唯一的事就是返回第一个大于 5 的元素。在其他语言中(例如 Python)可以用迭代器来解决此类问题。这些迭代器是一个惰性集合,只在请求时处理数据。如果用 JavaScript 的惰性迭代器代替上面的一系列数组方法,则需要进行如下步骤: -* `[0]` requests the first item from `filter` -* `filter` requests items from `map` until it has found one item that passes the predicate and yields (‘returns’) it -* `map` has processed an item for each time `filter` requested it +* `[0]` 请求经 `filter` 操作后数组的第一个元素 +* `filter` 从 `map` 中请求元素,直到发现一个符合条件的元素,并返回(‘yield’)它 +* 每当 `filter` 发送一次请求,`map` 便处理一个元素 -Here we did only `map` and `filter` the first tree items in the array because no more items where requested from the iterator. There where also no additional arrays or iterators constructed because every item goes through the entire pipeline one after the other. This is a concept that **can** result in massive performance gains when processing a lot of data. +在本例中,我们只对数组中的第一项进行了 `map` 和 `filter` 操作,接着迭代器就不会再请求其它项。这样也不需要另外构建数组或迭代器,因为每一项都是一步接一步地完成整个管道。因此,**惰性管道**这个概念**可以**在处理大量数据时获得巨大的性能收益。 -## Generators and iterators in JavaScript +## JavaScript 中的生成器和迭代器 -Luckily for us JavaScript does actually support the concept of [iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators). They can be created with [generator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) functions that yield the items of the collection. A generator function looks as follows: +幸运的是 JavaScript 确实支持[迭代器](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators)的概念。可以使用[生成器](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators)函数来创建集合中的各个元素。一个生成器函数如下: ```TypeScript function* iterator() { @@ -49,7 +49,7 @@ for(let x of iterator()) { } ``` -Here the for-loop will request an item of the iterator for each loop. The generator function uses the `yield` keyword to return the next item in the collection. As you see we can yield multiple times to create iterators that contain multiple items. There will never be any array constructed in memory. We can make this a bit better to understand when we remove some of the syntax sugar: +在这里,for 循环将在每次循环中请求迭代器的一个元素。生成器函数使用 `yield` 关键字来返回集合中的下一项。如你所见,我们可以多次生成包含多个项的迭代器,因此永远不需要在内存中构造额外的数组。我们可以删除一些语法糖方便理解: ```TypeScript const itt = iterator() @@ -61,17 +61,17 @@ while(current.done == false) { } ``` -Here you can see that an iterator has a `next` method for requesting the next item. The result of this method has the value and a boolean indicating of we have more results left in the iterator. While this all very interesting, we will need some more things if we want to construct proper data pipelines with iterators: +你可以看到迭代器有一个 `next` 方法用于请求下一项,此方法的将返回一个值和一个布尔值,布尔值用于指示迭代器中是否还有更多结果。虽然这一切都很有趣,但如果我们想要使用迭代器构建正确的数据管道,还需要做更多的事情: -* conversions from arrays to iterators and back -* iterators that operate on other iterators, like `map` and `filter` (also called ‘higher-order iterators’) -* a proper interface to chain all of those together in an elegant and practical way +* 从数组到迭代器的转换 +* 在其它迭代器上运作的迭代器,如 `map` 和 `filter`(也称为“高阶迭代器”) +* 一个合适的接口,以优雅和实用的方式将所有步骤链接在一起 -In the rest of this article I will show how to do those things. At the end I have included a link to a library I created that contains a lot more features. Sadly, this is not a native implementations of lazy iterators. This means that there is overhead and in a lot of cases this library is not worth it. But I still want to show you the concept in action and discuss its pros and cons. +下面,我将展示如何实现这些功能。在文末我留了一个链接,指向我创建的有着更多功能的库。遗憾的是,这不是惰性迭代器的原生实现,这也意味着用这个库存在额外开销,而且导致在一些情况下不值得用它。但我还是想向你们展示这个概念的实际应用,并讨论它的利弊。 -## Iterator constructors +## 迭代器的构造函数 -We want to be able to create iterators from multiple data sources. The most oblivious one is arrays. This one is quite easy, we loop over the array and yield all items: +我们希望能够从多个数据源创建迭代器。最容易被遗忘的就是数组。这是相当容易的,我们循环数组,并产生所有项目: ```TypeScript function* from_array(a:a[]) { @@ -79,7 +79,7 @@ function* from_array(a:a[]) { } ``` -Turning an iterator in an array will require us to call `next` until we have gotten all the items. After this we can return our array. Of course you only want to turn an iterator into an array when absolutely needed because this function causes a full iteration. +在数组中可以用 `next` 调用迭代器,直到获得所有的数组元素。当然希望你只在别无选择时再将迭代器转换回数组,因为这个函数需要进行一次完整的迭代: ```TypeScript function to_array(a: Iterator) { @@ -93,7 +93,7 @@ function to_array(a: Iterator) { } ``` -Another method for reading data from an iterator is `first`. Its implementation is shown bellow. Note that it only request the first item from the iterator! This means that all the following potential values will never be calculated, resulting in less waste of resources in the data pipeline. +从迭代器中读取数据的另一种方法是 `first`,它的实现如下所示。注意,它只向迭代器请求第一项,这也意味着剩下的值将永远不会被计算到,从而减少数据管道中的资源浪费。 ```TypeScript export function first(a: Iterator) { @@ -101,11 +101,11 @@ export function first(a: Iterator) { } ``` -In the complete library there are also constructors that create iterators from [functions](https://github.com/WimJongeneel/ts-lazy-collections/blob/master/src/main.ts#L65-L74) or [ranges](https://github.com/WimJongeneel/ts-lazy-collections/blob/master/src/main.ts#L57-L63). +在完整的库中还有一些构造函数,它们会从 [functions](https://github.com/WimJongeneel/ts-lazy-collections/blob/master/src/main.ts#L65-L74) 或 [ranges](https://github.com/WimJongeneel/ts-lazy-collections/blob/master/src/main.ts#L57-L63) 创建迭代器。 -## Higher-order iterators +## 高阶迭代器 -A higher-order iterator transforms an existing iterator into a new iterator. Those iterators are what makes up the operations in a pipeline. The well-known transform function `map` is shown bellow. It takes an iterator and a function and returns a new iterator where the function is applied to all items in the original iterator. Note that we still yield item-for-item and preserve the lazy nature of the iterators while transforming them. This is very important if we want to actually achieve the higher efficiency I talked about in the intro of this article! +高阶迭代器会将现有的迭代器转换为新的迭代器,这些迭代器组成了管道中的操作。著名的转换函数 `map` 如下所示。它接受一个迭代器和一个函数,并返回一个新的迭代器,其中该函数应用于原始迭代器中的所有项。请注意,我们仍然会一项一项地生成(yield),并在转换迭代器时保留迭代器的惰性性质,这也是实现这篇文中所说的“更高效率”的关键点。 ```TypeScript function* map(a: Iterator, f:(a:a) => b){ @@ -117,7 +117,7 @@ function* map(a: Iterator, f:(a:a) => b){ } ``` -Filter can be implemented in a similar way. When requested for the next item, it will keep requesting items from its inner iterator until it has found one that passed the predicate. This item will be yielded and execution is halted until the request for the next item comes in. +过滤器可以用类似的方式实现。当请求下一项时,它将一直从内部迭代器请求元素,直到找到一个通过条件的元素,生成(yield)此项,并停止执行迭代,直到收到生成下一个元素的请求。 ```TypeScript function* filter(a: Iterator, p: (a:a) => boolean) { @@ -129,11 +129,11 @@ function* filter(a: Iterator, p: (a:a) => boolean) { } ``` -Many more higher-order iterators can be constructed in with the same concepts I have show above. The complete library ships with a lot of them, check them out over [here](https://github.com/WimJongeneel/ts-lazy-collections#collection-methods). +可以用上面介绍的概念构造更多的高阶迭代器。[完整的库](https://github.com/WimJongeneel/ts-lazy-collections#collection-methods)中有更多种类的高阶迭代器用于参考,欢迎访问。 -## The builder interface +## 构建器接口 -The last part of the library is the public facing API. The library uses the builder pattern to allow you to chain methods like on arrays. This is done by creating a function that takes an iterator and returns an object with the methods on it. Those methods can call the constructor again with an updated iterator for the chaining: +库的最后一部分是面向用户的 API。该库使用了构建器模式,来让你像在数组上那样进行链式调用。这是通过创建一个接受迭代器,并返回带有方法的对象的函数来完成的。这些方法可以再次调用构造函数与更新迭代器的链接: ```TypeScript const fromIterator = (itt: Iterator) => ({ @@ -144,7 +144,7 @@ const fromIterator = (itt: Iterator) => ({ }) ``` -The example of the start of this article can be written as bellow. In this implementation we don’t create additional arrays and only process the data that is actually used! +本文开头的例子可以写成如下形式。在这个实现中,我们不再需要创建额外的数组,只需要处理实际使用的数据! ```TypeScript const x = fromIterator(from_array([1,2,3,4,5])) @@ -153,11 +153,11 @@ const x = fromIterator(from_array([1,2,3,4,5])) .first() ``` -## Conclusion +## 结论 -In this article I have shown you how generators and iterators can be used to create a powerful and very efficient library for processing lots of data. Of course iterators are not the golden bullet that will fix everything. The gains in efficiency are down to saving on unnecessary calculations. How much this means in real numbers is completely down to how much calculations there can be optimized out, how heavy those calculations are and how much data you are processing. When there are no calculations to save or the collections are relative small, you will potentially lose performance to the overhead of the library. +在本文中,我向您展示了如何使用生成器和迭代器来创建功能强大且非常高效的库来处理大量数据。当然,迭代器并不是解决所有问题的金钥匙。效率的提高是由于节省了不必要的计算。实际上提升了多少,完全取决于可以优化多少计算、这些计算有多繁重以及要处理多少数据。当没有要保存的计算或集合相对较小时,你可能会因为库的开销而损失性能。 -The full source code can be found on [Github](https://github.com/WimJongeneel/ts-lazy-collections#collection-methods) and contains more features that fitted in this article. I would love to hear your opinion on this. Do you think it is a pity that JavaScript doesn’t use lazy iteration for the array methods? And do you think that using generators is the way forward for collections in JavaScript? If JavaScript would use lazy iterators by default they should be able to optimize the overhead away (like other languages have done) while still preserving the potential wins with efficiency. +完整的源代码可以在 [Github](https://github.com/WimJongeneel/ts-lazy-collections#collection-methods) 找到并包含本文中包含的更多特性。我很想听听你对此的意见。你是否认为 JavaScript 不对数组方法使用惰性迭代是很可惜的?你是否认为使用生成器是 JavaScript 集合的前进方向?如果JavaScript在默认情况下使用惰性迭代器,则它们应该能够优化开销(就像其他语言一样),同时仍然保持效率的潜在优势。 > 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 diff --git a/TODO1/how-to-be-a-good-remote-developer.md b/TODO1/how-to-be-a-good-remote-developer.md index 9759e097a5f..ecee1e771e0 100644 --- a/TODO1/how-to-be-a-good-remote-developer.md +++ b/TODO1/how-to-be-a-good-remote-developer.md @@ -2,90 +2,90 @@ > * 原文作者:[John Au-Yeung](https://medium.com/@hohanga) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/how-to-be-a-good-remote-developer.md](https://github.com/xitu/gold-miner/blob/master/TODO1/how-to-be-a-good-remote-developer.md) -> * 译者: -> * 校对者: +> * 译者:[Badd](https://juejin.im/user/5b0f6d4b6fb9a009e405dda1) +> * 校对者:[zhanght9527](https://github.com/zhanght9527), [QinRoc](https://github.com/QinRoc) -# How to Be a Good Remote Developer +# 如何成为一名优秀的远程开发者 -![Photo by [Nicole Wolf](https://unsplash.com/@joeel56?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)](https://cdn-images-1.medium.com/max/12000/0*gcJQWqRaHf8E66Bw) +![图片来自 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 的 [Nicole Wolf](https://unsplash.com/@joeel56?utm_source=medium&utm_medium=referral)](https://cdn-images-1.medium.com/max/12000/0*gcJQWqRaHf8E66Bw) -Being a good remote worker takes a lot of discipline. We have to do everything ourselves without anyone watching. +要成为一名优秀的远程办公人士,需要极强的自制力。我们需要在无人监督的环境中独自完成每一项工作。 -In this article, we’ll look at some habits we can adopt to become a good remote developer. +在本文中,我们将介绍一些可以帮我们成为优秀远程开发者的习惯。 -## Practice Good Meeting Etiquette +## 养成良好的会议礼仪 -Good meeting etiquette is very important. Since we're starting at our screen for meetings and don’t have to go to a physical meeting room, it’s all on us to be a practice good meeting etiquette. +良好的会议礼仪极其重要。由于我们只需要在屏幕前参加会议,不用进入真实的会议室,所以想要养成良好的会议礼仪,全凭自我约束。 -We should have everyone on separate screens. Our face should be on video if we’re participating. +我们应该把每个人的图像都放在单独的窗口里。当我们参与讨论时,应保持脸部出现在视频画面里。 -Also, we have to pay attention and don’t get distracted by random things around us. +同时,我们必须全神贯注,不能被身边琐事干扰。 -If we don’t have anything urgent, then we should do other things. +除非有其他紧急事情,否则的话,有这么几件事是我们应该做的。 -Being on the computer, it’s easy to take notes, so we should do it. +应该保持人在电脑旁,这样方便记笔记。 -Also, we should probably wear work attire if we turn on video so that we won’t embarrass ourselves if we stand up and have underwear on. +另外,如果开启了摄像头,我们应该着装端正,这样就不会出现起身时内衣意外上镜的尴尬场面。 -## Experiment With What Makes Us the Most Productive +## 尝试能让我们以最高效率产出的方式 -Since we aren’t using only office equipment and staying in the office, we can experiment with our home office setup to see what works for us. +既然我们不仅仅是使用办公设备,更是生活在办公空间中,我们尽可以尝试不一样的家庭办公方式,看看哪些适合我们。 -We can wear whatever makes us comfortable as long it’s not underwear or any non-business clothing. +除了内衣和其他非正式的着装,我们大可以让自己穿得舒服一些。 -Also, we can shift our start and end times a bit as long we don’t miss any meetings. +我们还可以稍稍地调整作息时间,只要不错过会议就行。 -The type of work that we do during the day can also change according to our schedule since no one is watching and micromanaging what we’re doing. +既然没人时刻监督和管理我们做什么,我们也可以根据日程调整当日的工作类型。 -We can listen to whatever we want and sit anywhere that makes us more comfortable. +我们可以听任何我们自己喜欢的音乐,可以坐在任何我们觉得更舒服的地方。 -Also, we probably don’t have any set lunch hour. That’s nice since that means we can adjust our lunch schedule the way we wish. +另外,我们的午餐时间可以不固定。我们可以想什么时候吃就什么时候吃,太棒了。 -## Clear Communication +## 清晰的沟通 -Since we don’t see each other in person, we can’t read people’s body language to get what they mean when they type a message or talk. +由于我们无法和其他人当面沟通,所以当通过打字或者语音电话进行沟通时,我们无法通过他们的肢体语言来理解他们的意思。 -Therefore, we should make sure that everything is clear and that people understand what we want to convey. +因此,我们应该确保把所有细节说清楚,以便于他人理解我们想传达的内容。 -Dealing with different time zones and different mediums of communication also makes things harder for us. +时区和沟通媒介的差异让远程沟通难上加难。 -Therefore, to make everyone’s lives easier, we should outline our ideas clearly and type out actionable steps. +所以,为了帮他人降低理解难度,我们应该清晰地描述我们的想法并给出可执行的步骤。 -Meeting notes are more important since we aren’t sitting beside each other to ask questions. +由于我们并不是坐在一起提出问题,会议记录的作用显得更加重要。 -Instead of using a real whiteboard, we have to use a virtual one. +没有真实的白板,我们就用白板软件。 -If we have to pair program, we have to talk and share our screens. +如果我们需要结对编程,我们应该开启语音通话并共享屏幕。 -Also, no one will know how we feel over the Internet, so we should convey those clearly. +另外,在网上,没人能感知我们的感受,所以我们应该把自己的感受清晰地传达出来。 -If we need help, we’ve to ask around since not everyone may answer. +当我们需要帮助,我们需要多方求助,因为不是每个人都能为我们解答。 -Also, we should check our notifications so that we’ll answer people when we see them. People are waiting for our answers and this is more important since no one is around us to answer urgent questions if we need to. +还有,我们应该经常检查消息通知,以便及时回复他人。别人在等我们的回复呢,当我们急于寻求答复但周围没有人能响应时,我们就更能体会这一点的重要性。 -![Photo by [Annie Spratt](https://unsplash.com/@anniespratt?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)](https://cdn-images-1.medium.com/max/15904/0*jlbf4_s4q88Oz-IR) +![图片来自 [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral) 的 [Annie Spratt](https://unsplash.com/@anniespratt?utm_source=medium&utm_medium=referral)](https://cdn-images-1.medium.com/max/15904/0*jlbf4_s4q88Oz-IR) -## Create Boundaries Between Work and Life +## 划清工作和生活的边界 -If we’re working 9 to 5 at the office, then we got to set similar boundaries even if we’re working at the office. +如果我们在办公室时的作息时间是朝九晚五,那么在家办公时,也应该设置相近的时间点。 -There’s just no way that we can work 24 hours a day and not burn out and be tired. +我们不可能一天工作 24 小时而不感到疲惫。 -Therefore, we should set some boundaries so that we cut off work the same way that we go home at the end of the day. +所以,我们应该设置些许边界来中止工作,就像从办公室里下班回家那样。 -At the end of the day, turn off work notifications, close all our work stuff and relax. Also, we should take breaks like we do when we’re in the office. +当一天结束时,关闭工作消息通知,收起所有工作相关事物,好好休息吧。另外,我们应该像在办公室里那样,适时休息、劳逸结合。 -We got to walk around and clear our minds. Also, lunch breaks are equally important, so we should have them. +我们得四处走走、放空头脑。而且,午休也相当重要,所以也应该午休。 -## Conclusion +## 总结 -Even though we’re working remotely, we got to set our boundaries like we do when we’re working at the office. +即使我们是在远程办公,我们也应该像在办公室上班那样,建立工作和生活的边界。 -The difference is that we have to make sure that we check our notifications more frequently during work hours so that we can answer people’s questions. +区别在于,我们要确保在工作时间内,更加频繁地查看消息通知,以便及时回复他人。 -Also, we should make sure that we convey everything clearly so that we can be sure that everyone understands us. +另外,我们应该确保清晰地传达意图,让他人准确领会我们的想法。 -Finally, to collaborate, we have to use share screens and use video conference software. +最后,我们要通过共享屏幕和视频会议软件来协同办公。 > 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 diff --git a/TODO1/image-manipulation-libraries-for-javascript.md b/TODO1/image-manipulation-libraries-for-javascript.md new file mode 100644 index 00000000000..e9cb2bc3a8c --- /dev/null +++ b/TODO1/image-manipulation-libraries-for-javascript.md @@ -0,0 +1,181 @@ +> * 原文地址:[10 JavaScript Image Manipulation Libraries for 2020](https://blog.bitsrc.io/image-manipulation-libraries-for-javascript-187fde1ad5af) +> * 原文作者:[Mahdhi Rezvi](https://medium.com/@mahdhirezvi) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/image-manipulation-libraries-for-javascript.md](https://github.com/xitu/gold-miner/blob/master/TODO1/image-manipulation-libraries-for-javascript.md) +> * 译者: +> * 校对者: + +# 10 JavaScript Image Manipulation Libraries for 2020 + +![](https://cdn-images-1.medium.com/max/2560/1*lXwMUm79vvrK_ZjazwqcbA.jpeg) + +Working with images in JavaScript can be quite difficult and cumbersome. Thankfully, there are a number of libraries that can make things a lot easier. Below are my favorite ones in different categories. + +If you’ve found something useful, try to wrap it as a component of your framework of choice. This way, you’d have a reusable component with a declarative API, always ready to be used. + +## 1. Pica + +This plugin helps you reduce upload size for large images thereby saving upload time. It allows you to resize images in your browser without pixelation and reasonably fast. It autoselects the best of available technologies out of web-workers, web assembly, createImageBitmap and pure JS. + +[Demo](http://nodeca.github.io/pica/demo/) +[Github](https://github.com/nodeca/pica) + +![](https://cdn-images-1.medium.com/max/2086/1*01gc8wM7mYZxRvzM592r-A.png) + +--- + +## 2. Lena.js + +This cool image library is very tiny in size but has around 22 image filters that are pretty cool to play around with. You can also create and add new filters to the Github repo as well. + +[Demo](https://fellipe.com/demos/lena-js/) +[Tutorial](https://ourcodeworld.com/articles/read/515/how-to-add-image-filters-photo-effects-to-images-in-the-browser-with-javascript-using-lena-js) +[Github](https://github.com/davidsonfellipe/lena.js) + +![](https://cdn-images-1.medium.com/max/2718/1*rLKUyfeo_LUvvcRr7cYN0Q.png) + +--- + +## 3. Compressor.js + +This is a simple JS image compressor that uses the Browser’s native [canvas.toBlob](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) API to handle the image compression. This allows you to set the compression output quality ranging from 0 to 1. + +[Demo](https://fengyuanchen.github.io/compressorjs/) +[Github](https://github.com/fengyuanchen/compressorjs) + +![](https://cdn-images-1.medium.com/max/2334/1*hp85KWNmfPftt0MFj_qtEA.png) + +--- + +## 4. Fabric.js + +Fabric.js allows you to easily create simple shapes like rectangles, circles, triangles and other polygons or more complex shapes made up of many paths, onto the HTML `\` element on a webpage using JavaScript. Fabric.js will then allow you to manipulate the size, position and rotation of these objects with a mouse. + +It’s also possible to change some of the attributes of these objects such as their colour, transparency, depth position on the webpage or selecting groups of these objects using the Fabric.js library. Fabric.js will also allow you to convert an SVG image into JavaScript data that can be used for putting it onto the `\` element. + +[Demo](http://fabricjs.com/) +[Tutorials](http://fabricjs.com/articles/) +[Github](https://github.com/fabricjs/fabric.js) + +![](https://cdn-images-1.medium.com/max/2000/1*XRnIeG6-8cZe9BGjt5Hf-w.png) + +--- + +## 5. Blurify + +This is a tiny(~2kb) library to blur pictures, with graceful downgrade support from `css` mode to `canvas` mode. This plugin works under three modes: + +* `css`: use `filter` property(`default`) +* `canvas`: use `canvas` export base64 +* `auto`: use `css` mode firstly, otherwise switch to `canvas` mode by automatically + +You are simply required to pass the images, blur value and mode to the function to get the blurred image-simple and efficient. + +[Demo](https://justclear.github.io/blurify/) +[Github](https://github.com/JustClear/blurify) + +![](https://cdn-images-1.medium.com/max/2590/1*9qSBhOXTK3ao_69WZDp0Cw.png) + +--- + +## 6. Merge Images + +This library allows you to easily compose images together without messing around with canvas. Canvas can be kind of a pain to work with sometimes, especially if you just need a canvas context to do something relatively simple like merge some images together. `merge-images` abstracts away all the repetitive tasks into one simple function call. + +Images can be overlaid on top of each other and repositioned. The function returns a Promise which resolves to a base64 data URI. Supports both the browser and Node.js. + +[Github](https://github.com/lukechilds/merge-images) + +![](https://cdn-images-1.medium.com/max/2000/1*xJZYntWFYwkMJ-ljBuB47g.png) + +--- + +## 7. Cropper.js + +This plugin is a simple JavaScript image cropper that allows you to crop, rotate, scale, zoom around your images in an interactive environment. It also allows the aspect ratios to be set as well. + +[Demo](https://fengyuanchen.github.io/cropperjs/) +[Github](https://github.com/fengyuanchen/cropperjs) + +![](https://cdn-images-1.medium.com/max/2000/1*zrOLnVUpw-97XRCZ2mFuaw.png) + +--- + +## 8. CamanJS + +It is a canvas manipulation library for Javascript. It’s a combination of a simple-to-use interface with advanced and efficient image/canvas editing techniques. It is very easy to extend with new filters and plugins, and it comes with a wide array of image editing functionality, which continues to grow. It’s complete library independent and works both in NodeJS and the browser. + +You can choose a set of preset filters or change properties such as brightness, contrast, saturation manually to get the desired output. + +[Demo](http://camanjs.com/examples/) +[Website](http://camanjs.com/) +[Github](https://github.com/meltingice/CamanJS/) + +![](https://cdn-images-1.medium.com/max/2000/1*ORO_SftbsqsTRQudlvfn2A.png) + +--- + +## 9. MarvinJ + +MarvinJ is a pure javascript image processing framework derived from Marvin Framework. MarvinJ is easy and powerful for many different image processing applications. + +Marvin provides many algorithms to manipulate colour and appearance. Marvin also detects features automatically. The ability to work with basic image features like edges, corners and shapes are fundamental to image processing. The plugin helps to detect and analyze the corners of objects in order to determine the position of the main object in the scene. Due to these points, it is possible to automatically crop out the object. + +[Website](https://www.marvinj.org/en/index.html) +[Github](https://github.com/gabrielarchanjo/marvinj) + +![](https://cdn-images-1.medium.com/max/2462/1*oC9aNZECOL97bXRZSdjp_Q.png) + +--- + +## 10. Grade + +This JS library produces complementary gradients generated from the top 2 dominant colours in supplied images. This allows your website to fill your div with a matching gradient derived from your image. This is an easy to use plugin that helps you keep your website visually pleasing. + +This plugin would be my personal pick out of this list as I have gone through so much trouble to achieve a similar output given by this plugin. + +**The HTML file** + +```html + +
+ +
+
+ +
+``` + +**The JS script** + +```html + + +``` + +[Demo](https://benhowdle89.github.io/grade/) +[Github](https://github.com/benhowdle89/grade) + +![](https://cdn-images-1.medium.com/max/2326/1*-SqADlYfholv_yjT9YY75Q.png) + +--- + +Hope you liked this article. If you think something deserves to be on this, feel free to comment your pick. + +Happy Coding! + + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[区块链](https://github.com/xitu/gold-miner#区块链)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/is-your-rest-api-ready-for-deployment-7-questions-to-help-you-decide.md b/TODO1/is-your-rest-api-ready-for-deployment-7-questions-to-help-you-decide.md new file mode 100644 index 00000000000..3eddc83b037 --- /dev/null +++ b/TODO1/is-your-rest-api-ready-for-deployment-7-questions-to-help-you-decide.md @@ -0,0 +1,108 @@ +> * 原文地址:[Is Your REST API Ready for Deployment? 7 Questions to Help You Decide](https://codeburst.io/is-your-rest-api-ready-for-deployment-7-questions-to-help-you-decide-a371de9faa76) +> * 原文作者:[Zac Johnson](https://medium.com/@zacjohnson) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/is-your-rest-api-ready-for-deployment-7-questions-to-help-you-decide.md](https://github.com/xitu/gold-miner/blob/master/TODO1/is-your-rest-api-ready-for-deployment-7-questions-to-help-you-decide.md) +> * 译者: +> * 校对者: + +# Is Your REST API Ready for Deployment? 7 Questions to Help You Decide + +[Building an API](https://codeburst.io/this-is-how-easy-it-is-to-create-a-rest-api-8a25122ab1f3) can seem daunting, and for good reason. A lot can — and [often does](https://medium.com/better-programming/tips-on-rest-api-error-response-structure-aebe726e7f94) — go wrong. Rather than going back to the drawing board after your REST API has already been deployed, it’s best to be meticulous and launch an excellent API from the start. + +How do you know when to say, “Hold on… let’s not deploy this API just yet”? + +The following questions can help you determine the answer. + +![](https://cdn-images-1.medium.com/max/2560/1*ol3WYuuPV0nhrE0tMNiiZg.jpeg) + +**Have You Effectively Documented Your REST API?** + +To be blunt, developers won’t want to use your API if it provides poor (or no) documentation. Design your documentation to walk developers through the important aspects of your API so it’s easy to use. Your documentation should also make it easy for developers to maintain and update the API. + +Create tutorials that: + +* Are publicly accessible +* Provide a glossary that defines terms +* Specify and define resources +* Explain your API’s methods + +If you don’t know where to begin, tools like [Raml](https://raml.org/) or [Swagger](https://swagger.io/) can assist you. + +**Does your REST API Use the Right Data Format?** + +You have a choice of data formats when designing your API, and the one you choose can determine success or failure. Since an API is a connection point between the client and the server, its data format needs to be user friendly for both sides. + +Popular formats include: + +* **Direct formats** **(like JSON, XML, YAML).** These formats are made to manage data in direct use with other systems. They’re excellent for interacting with other APIs. +* **Feed formats (like RSS, SUP, Atom).** These are primarily used for the serialization of updates from social media and blogs. +* **Database formats (like SQL, CSV).** These are well suited for database-to-database communication as well as database-to-user. + +**Did You Effectively Name Your REST API’s Endpoints? + +** Endpoints identify the locations of resources and specify how these resources can be accessed. + +You should follow good REST practices for naming your endpoints, including: + +* **Easy resource names.** Don’t make developers guess about resource names or force them to sift through documentation to discover how to find resources. Make sure the names are intuitive from the start. +* **Use nouns in URIs.** A RESTful URI should include a noun (like “/photos”) and exclude verbs. I.e., don’t use a name like “/createPhotos.” Don’t use any CRUD (Create, Read, Update, Delete) conventions here. +* **Keep resource names plural.** For consistency, it’s advisable to use “/photos” or “/settings” (rather than “/photo”). +* **Use hyphens, not underscores.** Some browsers hide underscores ( _ ). Hyphens (-) are easier to see. +* **Lowercase letters.** URI elements are case sensitive (except the scheme and host components). For consistency, stick to lowercase. + +**Did You Properly Version Your REST API?** + +Your API will undergo changes over time, and you’ll need to manage these changes. Keep your older API versions active and maintained for people who are still using them. + + Proper versioning helps minimize occurrences of invalid requests being sent to newly updated endpoints. + +Methods of versioning can include: + +* Adding a version number as an attribute within custom request headers +* Parameter versioning, which means adding the version number as a query parameter +* Including the current version number into the URI +* Media type versioning, or altering the accept header to reflect the current version + +**Did You Test Your REST API?** + +Testing has traditionally happened on the UI side, which focuses on aspects like the site’s user experience. + +But because the API connects to the data layer as well as the UI, API testing is now recognized as a crucial task. By [testing your REST API](https://www.sisense.com/blog/rest-api-testing-strategy-what-exactly-should-you-test/), you’re ensuring its performance and functionality. + +Types of API testing include: + +* **Performance testing.** As the name suggests, this enables you to get a clear picture of the performance of your API. Performance testing includes both functional tests and load tests. +* **Unit testing.** A unit can be an endpoint, an HTTP method (GET, POST, PUT, DELETE), request headers, or request body. +* **Integration testing.** This is perhaps the most frequently used type of API test. Your API is at the heart of the integrations between data, applications and users’ devices. Testing such integration is therefore vital. +* **End to end testing.** This type of testing enables you to confirm the smooth flow of data between API connections. + +**Have You Established Security Measures for Your REST API?** + +Hackers are getting smarter and stronger in the 2020s. You have to be hyper-vigilant. + +Good security practices include: + +* **Use Only HTTPS.** Using only HTTPS protects credentials like passwords, JSON Web Tokens, API keys, etc. Also, include a time stamp to HTTP requests. This enables the server to only accept requests within a designated time frame to prevent hackers attempts at replay attacks. +* **Restrict allowed HTTP methods**. Reject any and all requests that aren’t on your permitted methods list. +* **Don’t let sensitive information show up in URLs.** Believe it or not, this sometimes happens. Make sure no passwords, usernames, API keys, etc. are appearing in any URLs. +* **Security checks.** Be thorough and take no chances. Protect your HTTP cookies and databases. Hide any sensitive data that may aggregate to your logs. Practice effective security password checking, and use CSRF tokens. + +**Did You Design Your REST API with Scalability in Mind?** + +You’ll need your API to process an increasing number of requests over time. It’s important to design it for scalability to maintain its performance and add new features as needed. + + Estimate your system’s load even before designing it. Assess the estimated the number of requests per second and the size of requests. Then, during the API’s uptime, frequently check its load distribution, response time, and other important metrics. + + Consider using a cloud service that’s able to automatically scale the system. That way, you won’t need to acquire expensive equipment to handle increasing amounts of data. + +**Ready to Run?** + +Taking your time and paying attention to details will save you from future frustration. At every stage of the process, ask yourself, “If I were a developer using this API, would I be happy with it?” + +If there are bugs or rough spots, you’re justified in pausing deployment to make everything right. + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[区块链](https://github.com/xitu/gold-miner#区块链)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/kafka-vs-rabbitmq-why-use-kafka.md b/TODO1/kafka-vs-rabbitmq-why-use-kafka.md index 3c6e4d264ac..843e4adba95 100644 --- a/TODO1/kafka-vs-rabbitmq-why-use-kafka.md +++ b/TODO1/kafka-vs-rabbitmq-why-use-kafka.md @@ -2,114 +2,114 @@ > * 原文作者:[SeattleDataGuy](https://medium.com/@SeattleDataGuy) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/kafka-vs-rabbitmq-why-use-kafka.md](https://github.com/xitu/gold-miner/blob/master/TODO1/kafka-vs-rabbitmq-why-use-kafka.md) -> * 译者: -> * 校对者: +> * 译者:[Roc](https://github.com/QinRoc) +> * 校对者:[icy](https://github.com/Raoul1996),[cyril](https://github.com/shixi-li) -# Kafka vs. RabbitMQ: Why Use Kafka? +# Kafka vs. RabbitMQ:为什么使用 Kafka? -> Are all data streaming services made equal? +> 所有的数据流服务都是一样的么? ![Photo by [Levi Jones](https://unsplash.com/@ev?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/data?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)](https://cdn-images-1.medium.com/max/5754/1*DJvGajoZpUGKsSSEFyzwwQ.jpeg) -A vital part of the successful completion of any project is the selection of the right tools for performing essential basic functions. For developers, the availability of several messaging services to pick from always poses a challenge. +任何项目的圆满完成都离不开选择正确的工具来实现必需的基础功能。对开发者而言,从多个消息服务中挑选出一个合适的,一直是一个挑战。 -One crucial question unanswered is the use of [Apache Kafka](https://kafka.apache.org/) or [RabbitMQ](https://www.rabbitmq.com/) services. Both platforms feature several functionalities and use cases that can help users make an informed decision. +一个悬而未决的重要问题是:选择 Apache Kafka 还是 RabbitMQ?这两个平台都有独特的功能和使用场景,了解这些可以帮助用户做出明智的选择。 -Apache Kafka and RabbitMQ are two top platforms in the area of messaging services. Although both platforms handle messaging differently, the difference lies in their selected architecture, design, and approach to delivery. +Apache Kafka 和 RabbitMQ 是消息服务领域的两大顶尖平台。这两个平台处理消息的方式存在差异,主要体现在它们的架构、设计和消息传递方式上。 -## But What Are Apache Kafka and RabbitMQ? +## 但是 Apache Kafka 和 RabbitMQ 到底什么是呢? -Apache Kafka and RabbitMQ are open-source platforms that are utilized for streaming data as well as come equipped with [pub/sub](https://www.rabbitmq.com/tutorials/tutorial-three-ruby.html) (which we will describe later) systems that are commercial — supported and used by several enterprises. +Apache Kafka 和 RabbitMQ 都是可用于流数据处理的开源平台,由多家企业支持并使用,同样也配备有商业化的[发布/订阅(pub/sub)](https://www.rabbitmq.com/tutorials/tutorial-three-ruby.html)系统(我们将在后面介绍)。 -#### What is Apache Kafka? +#### Apache Kafka 是什么? -Apache Kafka, in simple terms, is a message bus optimized for high-access data replays and streams. The robust message broker allows applications to continually process and re-process stream data. +简而言之,Apache Kafka 是针对高速存取数据的重放和流而优化的消息总线。Kafka 健壮的消息代理使应用程序可以连续地处理和重新消费流数据。 -The open-source platform uses an uncomplicated and easy routing approach that engages a routing key in sending messages to a topic. Launched in 2011, the Kafka tool was created for streaming setups. +这个开源平台使用了一种简单易行的路由方法,该方法使用了路由键(routing key)来将消息发送到某个主题(topic)。Kafka 于 2011 年推出,它为流处理体系而生。 -#### What is RabbitMQ? +#### RabbitMQ 是什么? -RabbitMQ is an all-round messaging broker with support protocols like Advanced Message Queuing Protocol (AMQP), MQ Telemetry Transport (MQTT), and Simple (or Streaming) Text-Oriented Messaging Protocol (STOMP). +RabbitMQ 是一个多功能的消息代理,它支持多种协议的,例如 高级消息队列协议(Advanced Message Queuing Protocol,AMQP),MQ 遥测传输(MQ Telemetry Transport,MQTT)和 面向文本的简单(或流)消息协议(Simple (or Streaming) Text-Oriented Messaging Protocol,STOMP)。 -RabbitMQ can handle use cases seeking high efficiency, like processing online payments. RabbitMQ can also serve as a message broker amongst microservices. +RabbitMQ 可以处理追求高效的场景,例如处理在线支付场景。RabbitMQ 也可以用作微服务间的消息代理。 -Launched in 2007, RabbitMQ started as a primary element in messaging and SOA systems. Nowadays, its expanded roles also include streaming use cases. +RabbitMQ 推出于 2007 年,甫一问世,就成为了消息处理和 SOA 系统的主要组件。现在,它已经覆盖了流处理的使用场景。 -So, if you are considering whether to use Apache Kafka or RabbitMQ, read on to learn about the difference in architectures, approaches, and their performance pros and cons. +如果你正在纠结是选用 Apache Kafka 还是 RabbitMQ,那么请继续阅读,进一步了解两者在架构、方法以及性能优缺点方面的差异。 -## Architecture Differences +## 架构差异 -#### Apache Kafka architecture +#### Apache Kafka 的架构 -The Apache Kafka architecture uses a high volume of publish-subscribe messages and a streams platform that is quick and scalable. The robust message store, like logs, makes use of server clusters that hold several records in topics (categories). +Apache Kafka 的架构使用了大量的发布-订阅消息和一个高速、可扩展的流平台。它的健壮的消息存储机制(如日志)利用了服务器集群来存储不同 topic(即类别)下的多条记录。 -[All Kafka messages feature a key, value, and timestamp](http://kth.diva-portal.org/smash/get/diva2:813137/FULLTEXT01.pdf). The smart consumer or dumb broker model do not attempt tracking on consumer messages and only retain unread messages. Apache Kafka holds all messages for a defined time frame. +[Kafka 的每条消息都由键、值和时间戳组成](http://kth.diva-portal.org/smash/get/diva2:813137/FULLTEXT01.pdf)。智能消费者(smart consumer)或哑代理(dumb broker)模型不会试图追踪消费者的消息,而只是保留未读消息。Apache Kafka 会在一个规定的时间范围内保留全部的消息。 -#### RabbitMQ architecture +#### RabbitMQ 的架构 -The RabbitMQ architecture makes use of an all-round message broker which entails variations in point-to-point, request/reply, and pub/sub communication designs. +RabbitMQ 的架构使用了多功能的消息代理,这个代理的设计综合了点对点、请求/响应和发布/订阅的多种通信方案的变体。 -The use of a dumb consumer and smart broker method allows for reliable message delivery to consumers with similar speed to that of a broker monitoring the state of consumers. +哑消费者(dumb consumer)和智能代理(smart broker)模型的运用可以为消费者可靠地投递消息,并且这种方法的速度与使用代理监控消费者状态的方法的速度不相上下。 -With the use of synchronous or asynchronous communication methods, the platform provides adequate support for several plugins including .NET, client libraries, Java, Node.js, Ruby, and more. +通过使用同步或异步的通信方法,该平台对包括 .NET、Java、Node.js、Ruby 在内的多种语言客户端库和其他插件提供了足够的支持。 -It also makes available distributed deployment scenarios alongside a multi-node cluster to cluster federation with zero reliance on external services. +RabbitMQ 还在不依赖外部服务的情况下,实现了分布式部署方案和多节点集群联合。 -With [RabbitMQ](http://kth.diva-portal.org/smash/get/diva2:813137/FULLTEXT01.pdf), publishers can transmit messages to exchanges, and retrieve messages from queues by consumers. The decoupling producers from lines through exchanges guarantee that producers are not troubled with hardcoded routing choices. +使用 [RabbitMQ](http://kth.diva-portal.org/smash/get/diva2:813137/FULLTEXT01.pdf),发布者可以将消息传输到交换机,消费者再从队列中取出消息。交换机将消息生产者从生产线中解耦出来,确保生产者不用担心硬编码的路由选择。 -## Publish/Subscribe (Pub/Sub) +## 发布/订阅(Pub/Sub) -Publish/subscribe is amongst the main messaging patterns for asynchronous messaging, a messaging scheme where message production is decoupled from its processing by a consumer. +发布/订阅是异步消息传递的主要模式之一。异步消息传递方案解耦了消息的生产与消费者对它的处理。 #### Apache Kafka -In Apache Kafka, the platform is created for high volume publish-subscribe messages and streams, which are intended to be durable, quick, and scalable. In essence, Kafka makes available a sustainable message store and a server cluster. +在 Apache Kafka 中,该发布/订阅平台是为大量发布-订阅消息和流而创建的,旨在持久、高速和可扩展。本质上,Kafka 带来了持久化的消息存储和服务器集群。 #### RabbitMQ -In RabbitMQ, the design entails an all-round message broker, using several variations of point-to-point, request/reply, and pub-sub communication style patterns. +RabbitMQ 的设计方案中有多功能的消息代理,这个代理基于点对点、请求/响应和发布-订阅通信模式的变体来实现。 -## Push/Pull Model +## 推/拉(Push/Pull)模型 -#### Apache Kafka: Pull-based method +#### Apache Kafka:基于拉(Pull)的方法 -Kafka makes use of a pull model where consumers make message requests in batches from a specified offset. Apache Kafka also allows for long-pooling, which stops tight loops when no message goes through the offset. +Kafka 使用拉模型,在该模型中,消费者从特定的偏移量开始批量地请求消息。Apache Kafka 还支持长轮询,当通过偏移量不再获取到消息时,长轮询会停止紧凑的轮询请求。 -The pull model remains logical for Apache Kafka due to its partitions. Its platform makes available message order within a barrier without contending consumers. +由于分区(partitions)的存在,Apache Kafka 使用拉取模型是合理的。Kafka 可以在没有消费者相互竞争的情况下提供消息排序。 -This approach lets users leverage message batching for efficient message delivery and better throughput. +这种方法让用户可以利用消息批处理来实现高效的消息传递,获取更高的吞吐量。 -#### RabbitMQ: Push-based method +#### RabbitMQ:基于推(Push)的方法 -RabbitMQ pushes messages to the consumer, which includes a prefetch limit configuration essential to preventing the consumer from becoming overwhelmed by multiple messages. +RabbitMQ 将消息推送给消费者,这个过程包括预读取限制的配置,该配置对于防止消费者被多个消息淹没至关重要。 -They can also be useful for low latency messaging. The purpose of the push method is the quick distribution of individual messages individually, alongside a guarantee that all of it is parallelized evenly and messages are processed in an ordered queue, usually as they arrive. +它们对于低延迟的消息传递也很有用。推送方法的目的是快速而独立地分发各个消息,在这个过程中保证所有的分发是均匀地并行进行的,并使消息能够按照到达的顺序获得处理。 -## Use Cases +## 使用案例 #### Apache Kafka -Apache Kafka provides an additional broker itself, for which it is best known and it is a popular element of the platform. The additional broker has been premeditated and marketed in the direction of stream processing setups. +众所周知,Apache Kafka 本身提供了一个额外的代理,该代理是这个平台的流行元素。这个额外的代理已经在流处理体系的方向上做了预先考虑和布局。 -Also, the addition of Kafka Streams serves as an alternative to streaming platforms like Apache Flink, Apache Spark, Google Cloud Data Flow, and Spring Cloud Data Flow. +另外,增加的 Kafka Streams 可以替代 Apache Flink、Apache Spark、Google Cloud Data Flow 和 Spring Cloud Data Flow 等流处理平台。 -The exceptional [use cases](https://kafka.apache.org/uses) documentation provides a detailed use case for Apache Kafka including its commit logs, event sourcing, log aggregation, metrics, web activity tracking, and more tasks. +Kafka 优秀的[案例文档](https://kafka.apache.org/uses)提供了详细的使用案例说明,包括提交日志、事件源、日志聚合、指标、Web 活动跟踪和更多其他任务。 #### RabbitMQ -RabbitMQ delivers an all-round messaging solution with widespread usage amongst web servers’ timely response to requests in place of enforced response to performing resource-heavy measures while users await the result. +RabbitMQ 提供了一种全面的消息传递解决方案,该方案广泛用于实现 Web 服务器对请求的及时响应,已经代替了让用户一直等待资源密集型计算的结果的响应方式。 -RabbitMQ is also excellent for distributing messages to multiple receivers as it offers a lot of [features for reliable delivery](http://www.rabbitmq.com/confirms.html), federation, management tools, routing, security, and [other functions](http://www.rabbitmq.com/features.html). +RabbitMQ 还非常适合于将消息分发给多个接收者,因为它提供了许多[实现可靠投递的功能](http://www.rabbitmq.com/confirms.html)、联合、管理工具、路由和安全性,还有一些[其他功能](http://www.rabbitmq.com/features.html)。 -With assistance from additional software, RabbitMQ can also effectively address several substantial uses cases. A combination with Apache Cassandra can provide access to stream history, or with the [LevelDB](https://github.com/google/leveldb) plugin for access to an “infinite” queue. +借助其他软件的帮助,RabbitMQ 还可以有效地解决几个实际使用案例。RabbitMQ 与 Apache Cassandra 的组合可以提供对流历史的访问,或者与 [LevelDB](https://github.com/google/leveldb) 插件一起提供对“无限”队列的访问。 -## Conclusion +## 结论 -Both [Apache Kafka and RabbitMQ](https://www.theseattledataguy.com/kafka-vs-rabbitmq-why-use-kafka/) platforms offer a variety of critical services intended to suit a lot of demands. +Apache Kafka 和 RabbitMQ 平台均提供了多种关键服务,以适应大量的需求。 -RabbitMQ is sufficient for simple use cases that entail low data traffic. Also, with RabbitMQ, other additional benefits include flexible routing prospects and priority queue options. +对于低数据流量的简单场景,RabbitMQ 就够用了。此外,RabbitMQ 还有其他的优势,例如灵活的路由预测和优先级队列选项。 -On the other hand, if the proposed use case features the need for massive data and high traffic, then Apache Kafka is worth considering. +另一方面,如果是需要大量数据和高流量的场景,那么 Apache Kafka 值得考虑。 > 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 diff --git a/TODO1/more-reasons-why-developers-should-blog.md b/TODO1/more-reasons-why-developers-should-blog.md index 214e7b5bbb2..8f8e89abba0 100644 --- a/TODO1/more-reasons-why-developers-should-blog.md +++ b/TODO1/more-reasons-why-developers-should-blog.md @@ -2,90 +2,90 @@ > * 原文作者:[John Au-Yeung](https://medium.com/@hohanga) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/more-reasons-why-developers-should-blog.md](https://github.com/xitu/gold-miner/blob/master/TODO1/more-reasons-why-developers-should-blog.md) -> * 译者: -> * 校对者: +> * 译者:[PingHGao](https://github.com/PingHGao) +> * 校对者:[samyu2000](https://github.com/samyu2000), [febrainqu](https://github.com/febrainqu) -# More Reasons Why Developers Should Blog +# 开发者应该写博客的各种原因 ![Photo by [Thought Catalog](https://unsplash.com/@thoughtcatalog?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)](https://cdn-images-1.medium.com/max/12000/0*HJLkHYtt8SbRtrra) -Blogging is a way to share what we learn with others. +写博客是我们与他人分享所学的知识和技能的一种方式。 -In this article, we’ll look at why developers should keep a blog. +在这篇文章中,我们将探讨为什么开发者应该有自己的博客。 -## Become a Better Writer +## 成为更好的作者 -By writing on our blogs repeatedly, we’ll become better writers. The more we write and adapt to better writing habits, we can become better writers. +通过不断地进行博客写作,可以提高我们的写作能力。我们写的越多,并培养更好写作的习惯,才能成为优秀的博客作者。 -Better writers are better communications and that’ll bring us more and better opportunities in the long run. +开发者的写作能力越强,就能越顺畅地与别人交流。从长远来看,也会获得更多、更好的机会。 -## Explaining Things to Others is the Best Way to Learn +## 向他人讲解是最好的学习方式 -There’s no question that explaining things to others is the best way to learn. +毫无疑问向他人讲解是最好的学习方式。 -We get to do research on our topics that we’re writing about and we have to be able to produce our own examples for any technical topic we’re writing about to explain our points. +我们需要对写作的话题进行研究,并举例说明,阐明自己的观点。 -Also, we’ve to able to explain the different aspects of the technologies ourselves. +同样,我们需要具备从不同角度介绍各种技术的能力。 -If we learn enough to explain to others without looking through online resources, then we know that we’re proficient at what we’ve learned. +对于某项技能,如果我们不需要查看在线资料,就能向别人讲解,我们就可以认为自己熟练掌握了这项技能。 -Blogging will let us internalize our knowledge by writing about it and practicing by producing examples as we explain the technologies in our blog. +在写博客的过程中,我们通过叙述相关的知识来加深理解,通过举例子来增加练习。 -## Create a Discussion and Learn New Things from That +## 创造一场讨论并从中学习新的知识 -We can create a discussion based on our blog and engage with our audience. +我们可以基于自己的博客进行一场讨论,并与我们的读者交流互动。 -If our blog has an audience then we’ll get a discussion going in no time. +如果有人浏览了我们的博客,我们就可以进行一场讨论 -And from the discussion, we learn new things from that. This is a win-win situation. We know that our blog has engagement from the audience, which is good. +然后我们就能从这场讨论中学到新的东西。这是一种双赢的情况。们得知自己的博客引起了别人的讨论参与, 这是一件好事。 -Also, we can learn new things from the discussions that we’re raising by posting our blog posts. +此外,通过参与博客内容引发的讨论,我们也能学到新东西。 -## Present Our Own View On a Topic +## 对某个话题发表自己的观点 -Even the topic we’re writing about already had lots of people writing on it, we can still write about it since people will appreciate new views on our existing topic. +哪怕我们正在论述的主题已经有很多人发表过相关的博客了,我们仍然可以论述。因为关于旧的主题的新观点总是受人欢迎的。 -As long as we aren’t copying someone else’s work directly, it’s fine to write on a topic that many people have written about before. +只要我们不是直接的抄袭他人的工作,就别人已经发表过观点的话题进行论述也是可以的。 -We all have our own style and we can present new angles on the same topic that people have written about before by presenting our own view on it. +每个人都有自己独特的风格,并且通过阐述我们自己的观点,可以呈现新的看待问题的角度,即使这个问题已经被很多人讨论过。 -Therefore, we should write our own pieces on a topic despite many people writing about them already. +因此,尽管可能已经有很多人论述过相关的话题了,我们也应该写一篇文章来表达自己的观点。 -## Read More and More for Writing a Blog +## 为了写博客,需要更多的阅读 -For most blog posts that we write, we have to do research so that we can write about them. +就大多数我们所写的博客而言,我们需要做一些调研才能够进行写作。 -There’s just no way that we hold enough information and insights on our heads to write stuff to put into our blogs. +我们大脑所存储的信息和观点肯定是不够的,所以仅依靠它们是不能完成我们的博客写作的。 -Therefore, we have to do lots of lots of research if we’re going to maintain a developer blog. +因此,如果我们要维护一个开发者博客,我们必须做大量的研究。 -This makes blogging a great way to learn about whatever we’re writing about and absorb the knowledge by explaining and writing examples. +这使博客成为一种了解我们正在撰写的内容并通过解释和编写示例来吸收知识的好方法。 -## Educate Others +## 教导别人 -Educating others is a good reason to maintain our own developer blog. With it, we can help each other learn by providing our own views and insights to help us learn, which in turn helps other people learn. +我们维护自己的开发者博客,还能起到教导别人的作用。有了它,我们可以通过提供自己的观点和见解互相学习,同时也是在帮助别人。 -There aren’t enough resources on developer topics, so getting readers is pretty much guaranteed if we keep maintaining and promoting our blog. +与开发有关的资源一直是不够的,所以如果我们持续维护和推广我们的博客,不愁没有读者。 -People will look at our stuff and learn them even though they might not be commenting on our blogs or be engaging with us. +人们会查阅我们的博客并学习相关内容,即使他们可能不会在我们的博客上发表评论或与我们交流。 -They’ll find our stuff when they’re looking for help and some people will stumble on our blog and they get the help they need. +当他们寻求帮助时,会看到我们的博客。有些人会在无意中发现我们的博客,得到他们需要的帮助。 ![Photo by [Andrew Neel](https://unsplash.com/@andrewtneel?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)](https://cdn-images-1.medium.com/max/12000/0*4SGrplnJ1EYYeSt4) -## Enjoyment +## 乐趣 -Blogging is a great hobby. It’s something that we can do for enjoyment. It’s a lost cost hobby as we can blog on sites like Medium or WordPress or web can pay a few bucks a month to blog in our own blog. +写博客是一种有益的爱好,也是我们享受生活的一种方式。这种爱好成本很低,因为只要我们每月花几块钱,就可以在 Medium、WordPress 等网站上进行博客写作。 -Both are great ways to spend time since we’ve to do lots of research and wire about stuff that we want to write about. +这两种方式都是消磨时间的好方法,因为我们要做大量的研究,还要把我们想写的东西联系起来。 -## Conclusion +## 总结 -We can use blogging as a hobby. It’s a great hobby since it’s cheap or free to set up a blog. +我们可以把写博客当作一种爱好。 这是一种有益的爱好,因为建立一个博客很便宜甚至免费。 -Also, we’ll help people they don’t comment on us. If we keep our blog for long enough, someone will stumble onto our blog eventually. +同时,那些不发表评论的读者也可能得到我们的帮助。如果我们的博客持续足够长的时间,最终会有人偶然发现我们的博客。 -Finally, we become better writers by practicing our writing skills by blogging and presenting our work in front of the audience worldwide. +最后,通过博客写作以及向全世界的读者展示我们的作品,我们训练了写作能力,成为优秀的博客作者也是有可能的。 > 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 diff --git a/TODO1/object-detection-metrics.md b/TODO1/object-detection-metrics.md index 36e004ae5ab..b7f856a1cf3 100644 --- a/TODO1/object-detection-metrics.md +++ b/TODO1/object-detection-metrics.md @@ -1,14 +1,14 @@ -原文地址:[Metrics for object detection](https://github.com/rafaelpadilla/Object-Detection-Metrics) -中文文档地址:[https://github.com/xitu/gold-miner/blob/master/TODO1/object-detection-metrics.md](https://github.com/xitu/gold-miner/blob/master/TODO1/object-detection-metrics.md) -译者: -校对: +原文地址:[Metrics for object detection](https://github.com/rafaelpadilla/Object-Detection-Metrics) +中文文档地址:[https://github.com/xitu/gold-miner/blob/master/TODO1/object-detection-metrics.md](https://github.com/xitu/gold-miner/blob/master/TODO1/object-detection-metrics.md) +译者:[PingHGao](https://github.com/PingHGao) +校对:[xionglong58](https://github.com/xionglong58), [shixi-li](https://github.com/shixi-li)

-## Citation -This work was accepted to be presented at IWSSIP 2020. If you use this code for your research, please consider citing: +## 引用 +这一工作已被 IWSSIP 2020 所接收并在会上展示。 如果您使用此代码进行研究,请考虑引用: ``` @article{padillaCITE2020, title = {Survey on Performance Metrics for Object-Detection Algorithms}, @@ -18,152 +18,153 @@ year = {2020} } ``` -# Metrics for object detection +# 目标检测评价标准 -The motivation of this project is the lack of consensus used by different works and implementations concerning the **evaluation metrics of the object detection problem**. Although on-line competitions use their own metrics to evaluate the task of object detection, just some of them offer reference code snippets to calculate the accuracy of the detected objects. -Researchers who want to evaluate their work using different datasets than those offered by the competitions, need to implement their own version of the metrics. Sometimes a wrong or different implementation can create different and biased results. Ideally, in order to have trustworthy benchmarking among different approaches, it is necessary to have a flexible implementation that can be used by everyone regardless the dataset used. +**目标检测**的不同研究和实现中没有统一的**评价标准**,这正是本项目的动机。尽管网上各类竞赛都使用它们自己的标准去评价目标检测任务的效果,但只有部分提供用于计算检测准确率的代码片段。 -**This project provides easy-to-use functions implementing the same metrics used by the the most popular competitions of object detection**. Our implementation does not require modifications of your detection model to complicated input formats, avoiding conversions to XML or JSON files. We simplified the input data (ground truth bounding boxes and detected bounding boxes) and gathered in a single project the main metrics used by the academia and challenges. Our implementation was carefully compared against the official implementations and our results are exactly the same. +如果不只考虑单一赛事,研究者需要实现他们自己的一套评价指标,以便在不同的数据集上评价他们的方法。而有时一个错误或者不同的实现方法会导致不同且有偏差的结果。为了获得值得信赖的能够应对不同的方法的标准,更理想的方式是提供一种灵活的实现方式,使得所有人都可以使用,无论使用的是什么数据集。 -In the topics below you can find an overview of the most popular metrics used in different competitions and works, as well as samples showing how to use our code. +**本项目提供了易于使用的各类函数,这些函数实现的评价标准与目标检测领域最受欢迎的几大竞赛使用的评价标准相同**。我们的实现不需要将你的检测模型修改为复杂的输入格式,避免了向 XML 文件或者 JSON 文件的转化。我们对输入数据进行了简化(bbox 标签以及检测结果)并且将学术界和各类竞赛主流的评价标准集合在了一起。我们将自己的实现与各类官方实现进行了仔细的对比,得到了完全一致的结果。 -## Table of contents +在接下来的章节你将会了解到在不同的竞赛和工作中最受欢迎的评价标准以及我们的代码的使用例程。 -- [Motivation](#metrics-for-object-detection) -- [Different competitions, different metrics](#different-competitions-different-metrics) -- [Important definitions](#important-definitions) -- [Metrics](#metrics) - - [Precision x Recall curve](#precision-x-recall-curve) - - [Average Precision](#average-precision) - - [11-point interpolation](#11-point-interpolation) - - [Interpolating all points](#interpolating-all-points) -- [**How to use this project**](#how-to-use-this-project) -- [References](#references) +## 目录 + +- [动机](#metrics-for-object-detection) +- [不同的竞赛,不同的标准](#different-competitions-different-metrics) +- [重要的定义](#important-definitions) +- [评价标准](#metrics) + - [精度×召回率曲线](#precision-x-recall-curve) + - [平均精度](#average-precision) + - [11 点插值法](#11-point-interpolation) + - [完全插值法](#interpolating-all-points) +- [**如何使用本项目**](#how-to-use-this-project) +- [参考](#references)
-## Different competitions, different metrics +## 不同的竞赛,不同的标准 -* **[PASCAL VOC Challenge](http://host.robots.ox.ac.uk/pascal/VOC/)** offers a Matlab script in order to evaluate the quality of the detected objects. Participants of the competition can use the provided Matlab script to measure the accuracy of their detections before submitting their results. The official documentation explaining their criteria for object detection metrics can be accessed [here](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/htmldoc/devkit_doc.html#SECTION00050000000000000000). The current metrics used by the current PASCAL VOC object detection challenge are the **Precision x Recall curve** and **Average Precision**. -The PASCAL VOC Matlab evaluation code reads the ground truth bounding boxes from XML files, requiring changes in the code if you want to apply it to other datasets or to your speficic cases. Even though projects such as [Faster-RCNN](https://github.com/rbgirshick/py-faster-rcnn) implement PASCAL VOC evaluation metrics, it is also necessary to convert the detected bounding boxes into their specific format. [Tensorflow](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/evaluation_protocols.md) framework also has their PASCAL VOC metrics implementation. +* **[PASCAL VOC Challenge](http://host.robots.ox.ac.uk/pascal/VOC/)** 提供了一个 MATLAB 脚本以评估检测结果的质量。在提交结果之前,参赛者可以使用该 MATLAB 脚本来评估他们的检测结果。关于该标准的官方解释可以在[这里](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/htmldoc/devkit_doc.html#SECTION00050000000000000000)查看。 PASCAL VOC 目标检测挑战目前使用的评价标准是**精度×召回率曲线**以及 **平均精度**。 +PASCAL VOC 的 MATLAB 版本评价代码从 XML 文件读取标签。当你需要应用到其它的数据集或者自定义的数据集时,就需要做出修改。尽管诸如 [Faster-RCNN](https://github.com/rbgirshick/py-faster-rcnn) 等工作实现了 PASCAL VOC 的评价标准,也需要把检测结果转化为特定的格式。 [Tensorflow](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/evaluation_protocols.md) 框架也实现了相应版本的 PASCAL VOC 评价标准。 -* **[COCO Detection Challenge](https://competitions.codalab.org/competitions/5181)** uses different metrics to evaluate the accuracy of object detection of different algorithms. [Here](http://cocodataset.org/#detection-eval) you can find a documentation explaining the 12 metrics used for characterizing the performance of an object detector on COCO. This competition offers Python and Matlab codes so users can verify their scores before submitting the results. It is also necessary to convert the results to a [format](http://cocodataset.org/#format-results) required by the competition. +* **[COCO Detection Challenge](https://competitions.codalab.org/competitions/5181)** 使用不同的标准来衡量不同的目标检测方法的准确率。[这里](http://cocodataset.org/#detection-eval)你可以找到解释 12 种用于确定一个目标检测器在 COCO 上的性能的标准的文档。该竞赛提供了 Python 和 MATLAB 版本的代码,以便参赛者在提交结果前检验他们的得分。同样,将输出转化为特定的[格式](http://cocodataset.org/#format-results) 也是必要的。 -* **[Google Open Images Dataset V4 Competition](https://storage.googleapis.com/openimages/web/challenge.html)** also uses mean Average Precision (mAP) over the 500 classes to evaluate the object detection task. +* **[Google Open Images Dataset V4 Competition](https://storage.googleapis.com/openimages/web/challenge.html)** 同样使用在 500 类别数据上的 mean Average Precision(mAP)来评估目标检测任务。 -* **[ImageNet Object Localization Challenge](https://www.kaggle.com/c/imagenet-object-detection-challenge)** defines an error for each image considering the class and the overlapping region between ground truth and detected boxes. The total error is computed as the average of all min errors among all test dataset images. [Here](https://www.kaggle.com/c/imagenet-object-localization-challenge#evaluation) are more details about their evaluation method. +* **[ImageNet Object Localization Challenge](https://www.kaggle.com/c/imagenet-object-detection-challenge)** 定义了一种考虑类别和 bbox 检测结果与标签的重叠区域的错误计算方式,为每张图片计算误差。最后总的误差是测试集所有图像最小的误差的均值。[这里](https://www.kaggle.com/c/imagenet-object-localization-challenge#evaluation) 是该方法的详细解释。 -## Important definitions +## 重要的定义 -### Intersection Over Union (IOU) +### 交并比 (IOU) -Intersection Over Union (IOU) is measure based on Jaccard Index that evaluates the overlap between two bounding boxes. It requires a ground truth bounding box ![](http://latex.codecogs.com/gif.latex?B_%7Bgt%7D) and a predicted bounding box ![](http://latex.codecogs.com/gif.latex?B_p). By applying the IOU we can tell if a detection is valid (True Positive) or not (False Positive). -IOU is given by the overlapping area between the predicted bounding box and the ground truth bounding box divided by the area of union between them: +交并比 (IOU) 是基于杰卡德系数的一种评估两个矩形框的重叠程度的测量方法。它需要有一个代表真实值的矩形框 ![](http://latex.codecogs.com/gif.latex?B_%7Bgt%7D) 以及预测值 ![](http://latex.codecogs.com/gif.latex?B_p)。通过计算 IOU 我们可以判断一个检测结果是有效的(True Positive)还是无效的(False Positive)。 +IOU 是预测的矩形框和标签矩形框的重叠部分面积与两者并集的比值:

-The image below illustrates the IOU between a ground truth bounding box (in green) and a detected bounding box (in red). +下图阐述了标签矩形框(绿色)和预测矩形框(红色)之间的IOU。

-### True Positive, False Positive, False Negative and True Negative +### True Positive, False Positive, False Negative 和 True Negative -Some basic concepts used by the metrics: +评价标准涉及到的一些基本的概念: -* **True Positive (TP)**: A correct detection. Detection with IOU ≥ _threshold_ -* **False Positive (FP)**: A wrong detection. Detection with IOU < _threshold_ -* **False Negative (FN)**: A ground truth not detected -* **True Negative (TN)**: Does not apply. It would represent a corrected misdetection. In the object detection task there are many possible bounding boxes that should not be detected within an image. Thus, TN would be all possible bounding boxes that were corrrectly not detected (so many possible boxes within an image). That's why it is not used by the metrics. +* **True Positive (TP)**: 一个正确的检测结果,其 IOU ≥ 阈值 +* **False Positive (FP)**: 一个错误的检测结果,其 IOU < 阈值 +* **False Negative (FN)**: 漏检一个目标 +* **True Negative (TN)**: 未使用,代表一个正确的漏检。在目标检测任务中,一幅图像中可能包含许多不应该被检测出来的标注框。因此 TN 应该指所有不应该被检测的矩形框(一幅图像中有太多的此类方框了)。所以评价方法中没有采用这一指标。 -_threshold_: depending on the metric, it is usually set to 50%, 75% or 95%. +阈值:根据标准的不同而变化,通常取 50%,75% 或者 95%。 -### Precision +### 精度 -Precision is the ability of a model to identify **only** the relevant objects. It is the percentage of correct positive predictions and is given by: +精度反应的是模型只检测正确物体的能力。它是模型预测结果中正确结果所占的比例,由下式计算:

-### Recall +### 召回率 -Recall is the ability of a model to find all the relevant cases (all ground truth bounding boxes). It is the percentage of true positive detected among all relevant ground truths and is given by: +召回率反应的是模型检测出所有存在的物体的能力(所有有标注的物体)。它是模型正确的检测结果占所有标注物体的比例,由下式计算:

-## Metrics +## 评价标准 -In the topics below there are some comments on the most popular metrics used for object detection. +接下来的内容将会包含对目标检测领域当前最为广泛使用的一些评价标准的介绍。 -### Precision x Recall curve +### 精度×召回率曲线 -The Precision x Recall curve is a good way to evaluate the performance of an object detector as the confidence is changed by plotting a curve for each object class. An object detector of a particular class is considered good if its precision stays high as recall increases, which means that if you vary the confidence threshold, the precision and recall will still be high. Another way to identify a good object detector is to look for a detector that can identify only relevant objects (0 False Positives = high precision), finding all ground truth objects (0 False Negatives = high recall). +精度×召回率曲线是评价目标检测算法一个很好的方式,因为在为每一类目标绘制该曲线的过程中置信度阈值是不断变化的。如果目标检测器对特定类别的精度随着召回率的增加而保持较高的水平,就意味着检测器有较好的性能。因为这意味着无论你怎么改变置信度阈值,精度和召回率都很高。另一个判断检测器好的方法是该检测器只会检测正确的物体(高精度)同时能够找到所有的物体(高召回率)。 -A poor object detector needs to increase the number of detected objects (increasing False Positives = lower precision) in order to retrieve all ground truth objects (high recall). That's why the Precision x Recall curve usually starts with high precision values, decreasing as recall increases. You can see an example of the Prevision x Recall curve in the next topic (Average Precision). This kind of curve is used by the PASCAL VOC 2012 challenge and is available in our implementation. +一个坏的检测器需要增加预测数量(降低精度)以便能够找到所有的物体(高召回率)。这就是为什么精度×召回率曲线通常以高精度开始,然后随着召回率增加逐渐降低。你可以在下一章节(Average Precision)看到精度×召回率曲线的例子。此类曲线被 PASCAL VOC 2012 挑战所使用并且被我们实现了。 -### Average Precision +### 平均精度 -Another way to compare the performance of object detectors is to calculate the area under the curve (AUC) of the Precision x Recall curve. As AP curves are often zigzag curves going up and down, comparing different curves (different detectors) in the same plot usually is not an easy task - because the curves tend to cross each other much frequently. That's why Average Precision (AP), a numerical metric, can also help us compare different detectors. In practice AP is the precision averaged across all recall values between 0 and 1. +另一种比较不同检测器性能的方法是计算精度×召回率曲线下的面积。由于 AP 曲线通常是上下波动的曲线,在同一幅图像中比较不同的曲线通常是很难的,因为这些曲线通常是频繁的相互交叉穿越的。这就是为什么平均精度(AP),一个数字标准,也能够帮助我们比较不同的检测器的好坏。在工程实际中,AP 是召回率在 0~1 范围内精度的平均值。 -From 2010 on, the method of computing AP by the PASCAL VOC challenge has changed. Currently, **the interpolation performed by PASCAL VOC challenge uses all data points, rather than interpolating only 11 equally spaced points as stated in their [paper](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.157.5766&rep=rep1&type=pdf)**. As we want to reproduce their default implementation, our default code (as seen further) follows their most recent application (interpolating all data points). However, we also offer the 11-point interpolation approach. +自 2010 年开始,PASCAL VOC 计算 AP 的方法已经改变。目前, **PASCAl VOC 使用的插值方法使用了所有的数据点,而不是他们[文章](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.157.5766&rep=rep1&type=pdf)所说的 11 个等间隔的点**。由于我们需要复现他们默认的标准,我们默认的代码遵循了他们最新的标准(使用所有的数据点)。然而,我们也提供了 11 个插值点的方法。 -#### 11-point interpolation +#### 11 点插值法 -The 11-point interpolation tries to summarize the shape of the Precision x Recall curve by averaging the precision at a set of eleven equally spaced recall levels [0, 0.1, 0.2, ... , 1]: +11 插值点方法试图通过计算召回率分别为 [0, 0.1, 0.2, ... , 1] 等 11 个值时精度的平均值来概括 PR 曲线的形状:

-with +其中

-where ![](http://latex.codecogs.com/gif.latex?%5Crho%5Cleft%20%28%20%5Ctilde%7Br%7D%20%5Cright%20%29) is the measured precision at recall ![](http://latex.codecogs.com/gif.latex?%5Ctilde%7Br%7D). +其中 ![](http://latex.codecogs.com/gif.latex?%5Crho%5Cleft%20%28%20%5Ctilde%7Br%7D%20%5Cright%20%29) 代表召回率为 ![](http://latex.codecogs.com/gif.latex?%5Ctilde%7Br%7D) 时的精度。 -Instead of using the precision observed at each point, the AP is obtained by interpolating the precision only at the 11 levels ![](http://latex.codecogs.com/gif.latex?r) taking the **maximum precision whose recall value is greater than ![](http://latex.codecogs.com/gif.latex?r)**. +AP 是通过计算 11 个点处的精度的平均值得到的。每个点的精度不是召回率为该点值时的精度,而是召回率大于该点值时精度所能达到的最大值。 -#### Interpolating all points +#### 完全插值法 -Instead of interpolating only in the 11 equally spaced points, you could interpolate through all points in such way that: +除了只使用 11 个均匀分布的点的数据,你还可以使用所有的点进行计算:

-with +其中

-where ![](http://latex.codecogs.com/gif.latex?%5Crho%5Cleft%20%28%20%5Ctilde%7Br%7D%20%5Cright%20%29) is the measured precision at recall ![](http://latex.codecogs.com/gif.latex?%5Ctilde%7Br%7D). +其中 ![](http://latex.codecogs.com/gif.latex?%5Crho%5Cleft%20%28%20%5Ctilde%7Br%7D%20%5Cright%20%29) 代表召回率为 ![](http://latex.codecogs.com/gif.latex?%5Ctilde%7Br%7D) 时的精度。 -In this case, instead of using the precision observed at only few points, the AP is now obtained by interpolating the precision at **each level**, ![](http://latex.codecogs.com/gif.latex?r) taking the **maximum precision whose recall value is greater or equal than ![](http://latex.codecogs.com/gif.latex?r+1)**. This way we calculate the estimated area under the curve. +在此种情况下,AP 现在是由**各个大小召回率**下的精度获取的,不是仅仅几个点。r 处的精度取**召回率大于等于 r+1 范围类的最大精度值**。由此我们可以估算曲线下的面积。 -To make things more clear, we provided an example comparing both interpolations. +为了更好地进行说明,我们提供了一个例子用来对两种方法进行比较。 -#### An ilustrated example +#### 一个图文并茂的例子 -An example helps us understand better the concept of the interpolated average precision. Consider the detections below: +一个例子能够帮助我们更好地理解如何通过插值计算平均精度。考虑如下的检测结果:

-There are 7 images with 15 ground truth objects representented by the green bounding boxes and 24 detected objects represented by the red bounding boxes. Each detected object has a confidence level and is identified by a letter (A,B,...,Y). +总共有7张图片,包含了 15 个绿色方框标示的真实标签以及 24 个红色方框标示的检测结果。每个检测结果都对应一个置信度水平,分别用一个字母表示(A,B,...,Y)。 -The following table shows the bounding boxes with their corresponding confidences. The last column identifies the detections as TP or FP. In this example a TP is considered if IOU ![](http://latex.codecogs.com/gif.latex?%5Cgeq) 30%, otherwise it is a FP. By looking at the images above we can roughly tell if the detections are TP or FP. +下表展示了各个矩形框以及对应的置信度。最后一列表明了这一检测结果是 TP 还是 FP。在本例中,如果 IOU ≥ 30%,则归为 TP;否则被归为 FP。通过粗略的观察上面的图片,我们能够大概判断检测结果是 TP 还是 FP。

@@ -198,9 +199,9 @@ The following table shows the bounding boxes with their corresponding confidence | Image 7 | Y | 95% | FP | ---> -In some images there are more than one detection overlapping a ground truth (Images 2, 3, 4, 5, 6 and 7). For those cases the detection with the highest IOU is considered TP and the others are considered FP. This rule is applied by the PASCAL VOC 2012 metric: "e.g. 5 detections (TP) of a single object is counted as 1 correct detection and 4 false detections”. +在某些图片中,一个真实的矩形框拥有了多个相重叠的检测结果(图像 2,3,4,5,6,7)。这对这些情况,拥有最大 IOU 的检测结果是 TP 而其他的检测结果为 FP。这一规则被 PASCAL VOC 2012 挑战所采用:“也就是说 5 个(TP)的检测结果被计数为 1 个正确的检测和 4 个错误的检测”。 -The Precision x Recall curve is plotted by calculating the precision and recall values of the accumulated TP or FP detections. For this, first we need to order the detections by their confidences, then we calculate the precision and recall for each accumulated detection as shown in the table below: +通过不断的累积 TP 或 FP 的检测结果,PR 曲线就被绘制出来了。为此,我们首先需要根据置信度对检测结果进行排序,然后我们随着检测结果的逐一累加不断地计算精度和召回率,结果如下表:

@@ -235,48 +236,48 @@ The Precision x Recall curve is plotted by calculating the precision and recall | Image 4 | O | 14% | 0 | 1 | 7 | 17 | 0.2916 | 0.4666 | ---> - Plotting the precision and recall values we have the following *Precision x Recall curve*: + 通过绘制精度和召回率的值我们将会得到如下的 **PR 曲线**:

-As mentioned before, there are two different ways to measure the interpolted average precision: **11-point interpolation** and **interpolating all points**. Below we make a comparisson between them: +如上文所说,有两种平均精度的差值计算方法:**11 点差值法**和**完全插值法**。接下来我们将对两种算法进行比较: -#### Calculating the 11-point interpolation +#### 计算 11 点插值法 -The idea of the 11-point interpolated average precision is to average the precisions at a set of 11 recall levels (0,0.1,...,1). The interpolated precision values are obtained by taking the maximum precision whose recall value is greater than its current recall value as follows: +该方法是求 11 个不同召回率(0, 0.1, ..., 1)对应的精度的平均值。各个点的精度插值对应的是召回率大于该处召回率的范围类精度的最大值。具体如下图:

-By applying the 11-point interpolation, we have: +使用 11 点插值计算方法有: ![](http://latex.codecogs.com/gif.latex?AP%20%3D%20%5Cfrac%7B1%7D%7B11%7D%5Csum_%7Br%5Cin%5C%7B0%2C0.1%2C...%2C1%5C%7D%7D%5Crho_%7B%5Ctext%7Binterp%7D%5Cleft%20%28r%5Cright%20%29%7D) ![](http://latex.codecogs.com/gif.latex?AP%20%3D%20%5Cfrac%7B1%7D%7B11%7D%20%5Cleft%20%28%201+0.6666+0.4285+0.4285+0.4285+0+0+0+0+0+0%20%5Cright%20%29) ![](http://latex.codecogs.com/gif.latex?AP%20%3D%2026.84%5C%25) -#### Calculating the interpolation performed in all points +#### 计算完全插值法 -By interpolating all points, the Average Precision (AP) can be interpreted as an approximated AUC of the Precision x Recall curve. The intention is to reduce the impact of the wiggles in the curve. By applying the equations presented before, we can obtain the areas as it will be demostrated here. We could also visually have the interpolated precision points by looking at the recalls starting from the highest (0.4666) to 0 (looking at the plot from right to left) and, as we decrease the recall, we collect the precision values that are the highest as shown in the image below: +通过使用所有的点,AP 可以看做是 PR 曲线的 AUC(Area Under Curve)的近似值。这么做的目的是减少曲线的震荡带来的影响。通过之前给出的公式,我们能够如后文所说获取这一面积。我们也能够通过从召回率最大值(0.4666)到 0 来(从右往左)观察确定各个插值处的精度;随着召回率降低,我们逐步确定各个阶段对应的最大精度,结果如下图所示:

-Looking at the plot above, we can divide the AUC into 4 areas (A1, A2, A3 and A4): +观察上图,我们可以将 AUC 分为 4 个部分(A1,A2,A3,A4):

-Calculating the total area, we have the AP: +计算所有的面积,我们将能够得到 AP: ![](http://latex.codecogs.com/gif.latex?AP%20%3D%20A1%20+%20A2%20+%20A3%20+%20A4) @@ -290,32 +291,32 @@ Calculating the total area, we have the AP: ![](http://latex.codecogs.com/gif.latex?AP%20%3D%200.24560955) ![](http://latex.codecogs.com/gif.latex?AP%20%3D%20%5Cmathbf%7B24.56%5C%25%7D) -The results between the two different interpolation methods are a little different: 24.56% and 26.84% by the every point interpolation and the 11-point interpolation respectively. +两种方法的 AP 有些许不同:24.56%(完全插值) 和 26.84%(11 点插值)。 -Our default implementation is the same as VOC PASCAL: every point interpolation. If you want to use the 11-point interpolation, change the functions that use the argument ```method=MethodAveragePrecision.EveryPointInterpolation``` to ```method=MethodAveragePrecision.ElevenPointInterpolation```. +我们默认的实现方式与 VOC PASCAL相同:每个点都进行插值。如果你想使用 11 点插值法,将函数的参数 ```method=MethodAveragePrecision.EveryPointInterpolation``` 改为 ```method=MethodAveragePrecision.ElevenPointInterpolation``` 即可。 -If you want to reproduce these results, see the **[Sample 2](https://github.com/rafaelpadilla/Object-Detection-Metrics/tree/master/samples/sample_2/)**. - +如果你想重现这些结果, 参考 **[例 2](https://github.com/rafaelpadilla/Object-Detection-Metrics/tree/master/samples/sample_2/)**. + -## How to use this project +## 如何使用本项目 -This project was created to evaluate your detections in a very easy way. If you want to evaluate your algorithm with the most used object detection metrics, you are in the right place. +本项目是为了轻松的对你的检测结果进行评估而创造的。如果你想用最为广泛使用的方法对你的算法进行评估,那么你来对地方了。 -[Sample_1](https://github.com/rafaelpadilla/Object-Detection-Metrics/tree/master/samples/sample_1) and [sample_2](https://github.com/rafaelpadilla/Object-Detection-Metrics/tree/master/samples/sample_2) are practical examples demonstrating how to access directly the core functions of this project, providing more flexibility on the usage of the metrics. But if you don't want to spend your time understanding our code, see the instructions below to easily evaluate your detections: +[例_1](https://github.com/rafaelpadilla/Object-Detection-Metrics/tree/master/samples/sample_1) and [例_2](https://github.com/rafaelpadilla/Object-Detection-Metrics/tree/master/samples/sample_2) 是两个非常实用的例子,阐述了如何直接使用本项目的核心函数,以便灵活的使用这些评价标准。但如果你不想花你的时间来理解我们的代码,看看下面的说明,以方便的评估你的检测结果: -Follow the steps below to start evaluating your detections: +按照以下步骤开始评估你的检测: -1. [Create the ground truth files](#create-the-ground-truth-files) -2. [Create your detection files](#create-your-detection-files) -3. For **Pascal VOC metrics**, run the command: `python pascalvoc.py` - If you want to reproduce the example above, run the command: `python pascalvoc.py -t 0.3` -4. (Optional) [You can use arguments to control the IOU threshold, bounding boxes format, etc.](#optional-arguments) +1. [创造标签文件](#create-the-ground-truth-files) +2. [创造检测文件](#create-your-detection-files) +3. 为使用 **Pascal VOC 评价标准**, 运行命令: `python pascalvoc.py` + 如果你想要复现上述例子, 运行命令: `python pascalvoc.py -t 0.3` +4. (可选) [你能够使用参数来控制 IOU 阈值,Bbox 的格式等等。](#optional-arguments) -### Create the ground truth files +### 创造标签文件 -- Create a separate ground truth text file for each image in the folder **groundtruths/**. -- In these files each line should be in the format: ` `. -- E.g. The ground truth bounding boxes of the image "2008_000034.jpg" are represented in the file "2008_000034.txt": +- 为文件夹 **groundtruths/** 中的每个图像创建一个单独的标签文本文件。 +- 在这些文件中,每一行的格式都应该为: ` `. +- 例如,图像 "2008_000034.jpg" 的矩形框的标签值记录在文件 "2008_000034.txt" 中: ``` bottle 6 234 45 362 person 1 156 103 336 @@ -323,7 +324,7 @@ Follow the steps below to start evaluating your detections: person 91 42 338 500 ``` -If you prefer, you can also have your bounding boxes in the format: ` ` (see here [**\***](#asterisk) how to use it). In this case, your "2008_000034.txt" would be represented as: +如果你喜欢,矩形框的格式也可以为: ` ` (参考此处 [**\***](#asterisk) 以了解如何使用). 此时, 你的 "2008_000034.txt" 内容应如下: ``` bottle 6 234 39 128 person 1 156 102 180 @@ -331,11 +332,11 @@ If you prefer, you can also have your bounding boxes in the format: ` ` (see here [**\***](#asterisk) how to use it). +- 为文件夹 **detections/** 中的每个图像创建一个单独的检测文本文件。 +- 检测文件的名称必须与其对应的标签文件名称匹配 (e.g. "detections/2008_000182.txt" 对应标签文件 "groundtruths/2008_000182.txt" 的检测结果)。 +- 在这些检测文件中,每行的格式应该为: ` ` (参考此处 [**\***](#asterisk) 了解如何使用)。 - E.g. "2008_000034.txt": ``` bottle 0.14981 80 1 295 500 @@ -345,31 +346,31 @@ If you prefer, you can also have your bounding boxes in the format: ` `. +如果你喜欢,Bbox 的格式可以为: ` `. -### Optional arguments +### 可选参数 Optional arguments: -| Argument                          | Description | Example | Default | +| 参数                          | 描述 | 例子 | 默认 | |:-------------:|:-----------:|:-----------:|:-----------:| -| `-h`,
`--help ` | show help message | `python pascalvoc.py -h` | | -| `-v`,
`--version` | check version | `python pascalvoc.py -v` | | -| `-gt`,
`--gtfolder` | folder that contains the ground truth bounding boxes files | `python pascalvoc.py -gt /home/whatever/my_groundtruths/` | `/Object-Detection-Metrics/groundtruths`| -| `-det`,
`--detfolder` | folder that contains your detected bounding boxes files | `python pascalvoc.py -det /home/whatever/my_detections/` | `/Object-Detection-Metrics/detections/`| -| `-t`,
`--threshold` | IOU thershold that tells if a detection is TP or FP | `python pascalvoc.py -t 0.75` | `0.50` | -| `-gtformat` | format of the coordinates of the ground truth bounding boxes [**\***](#asterisk) | `python pascalvoc.py -gtformat xyrb` | `xywh` | -| `-detformat` | format of the coordinates of the detected bounding boxes [**\***](#asterisk) | `python pascalvoc.py -detformat xyrb` | `xywh` | | -| `-gtcoords` | reference of the ground truth bounding bounding box coordinates.
If the annotated coordinates are relative to the image size (as used in YOLO), set it to `rel`.
If the coordinates are absolute values, not depending to the image size, set it to `abs` | `python pascalvoc.py -gtcoords rel` | `abs` | -| `-detcoords` | reference of the detected bounding bounding box coordinates.
If the coordinates are relative to the image size (as used in YOLO), set it to `rel`.
If the coordinates are absolute values, not depending to the image size, set it to `abs` | `python pascalvoc.py -detcoords rel` | `abs` | -| `-imgsize ` | image size in the format `width,height` .
Required if `-gtcoords` or `-detcoords` is set to `rel` | `python pascalvoc.py -imgsize 600,400` | -| `-sp`,
`--savepath` | folder where the plots are saved | `python pascalvoc.py -sp /home/whatever/my_results/` | `Object-Detection-Metrics/results/` | -| `-np`,
`--noplot` | if present no plot is shown during execution | `python pascalvoc.py -np` | not presented.
Therefore, plots are shown | +| `-h`,
`--help ` | 显示帮助信息 | `python pascalvoc.py -h` | | +| `-v`,
`--version` | 检查版本 | `python pascalvoc.py -v` | | +| `-gt`,
`--gtfolder` | 保存标签文件的文件夹 | `python pascalvoc.py -gt /home/whatever/my_groundtruths/` | `/Object-Detection-Metrics/groundtruths`| +| `-det`,
`--detfolder` | 保存检测文件的文件夹 | `python pascalvoc.py -det /home/whatever/my_detections/` | `/Object-Detection-Metrics/detections/`| +| `-t`,
`--threshold` | 决定检测结果为 TP 还是 FP 的阈值 | `python pascalvoc.py -t 0.75` | `0.50` | +| `-gtformat` | 标签文件中 Bbox 的存储格式 [**\***](#asterisk) | `python pascalvoc.py -gtformat xyrb` | `xywh` | +| `-detformat` | 检测文件中 Bbox 的存储格式 [**\***](#asterisk) | `python pascalvoc.py -detformat xyrb` | `xywh` | | +| `-gtcoords` | 标签文件的 Bbox 坐标的参考值。
如果是通过图像尺寸归一化后的结果 (如 YOLO 一样), 设置为 `rel`。
如果未经图像尺寸归一化,是实际值, 设置为 `abs` | `python pascalvoc.py -gtcoords rel` | `abs` | +| `-detcoords` | 检测文件的 Bbox 坐标的参考值。
如果是通过图像尺寸归一化后的结果 (如 YOLO 一样), 设置为 `rel`.
如果未经图像尺寸归一化,是实际值, 设置为 `abs` | `python pascalvoc.py -detcoords rel` | `abs` | +| `-imgsize ` | 图像尺寸,格式为 `width,height` .
如果 `-gtcoords` 或者 `-detcoords` 为 `rel` ,则需要提供该参数| `python pascalvoc.py -imgsize 600,400` | +| `-sp`,
`--savepath` | 保存绘图结果的文件夹 | `python pascalvoc.py -sp /home/whatever/my_results/` | `Object-Detection-Metrics/results/` | +| `-np`,
`--noplot` | 如果提供该参数,则执行过程中不会有图像展示 | `python pascalvoc.py -np` | not presented.
Therefore, plots are shown | -(**\***) set `-gtformat xywh` and/or `-detformat xywh` if format is ` `. Set to `-gtformat xyrb` and/or `-detformat xyrb` if format is ` `. +(**\***) 如果格式为 ` `, 设置应为 `-gtformat xywh` and/or `-detformat xywh`。如果格式为` `,设置应为 `-gtformat xyrb` and/or `-detformat xyrb` if format is ` `. -## References +## 参考 * The Relationship Between Precision-Recall and ROC Curves (Jesse Davis and Mark Goadrich) Department of Computer Sciences and Department of Biostatistics and Medical Informatics, University of diff --git a/TODO1/performant-javascript-best-practices.md b/TODO1/performant-javascript-best-practices.md index bcd8b33e01a..0673a9ba43f 100644 --- a/TODO1/performant-javascript-best-practices.md +++ b/TODO1/performant-javascript-best-practices.md @@ -2,46 +2,46 @@ > * 原文作者:[John Au-Yeung](https://medium.com/@hohanga) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/performant-javascript-best-practices.md](https://github.com/xitu/gold-miner/blob/master/TODO1/performant-javascript-best-practices.md) -> * 译者: -> * 校对者: +> * 译者:[IAMSHENSH](https://github.com/IAMSHENSH) +> * 校对者:[niayyy-S](https://github.com/niayyy-S), [xionglong58](https://github.com/xionglong58) -# Performant JavaScript Best Practices +# 高性能 JavaScript 最佳实践 ![Photo by [Jason Chen](https://unsplash.com/@ja5on?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)](https://cdn-images-1.medium.com/max/5760/0*UyQ42ciE79LF-bK4) -Like any program, JavaScript programs can get slow fast if we aren’t careful with writing our code. +与其它程序一样,如果我们不细心编写代码,JavaScript 程序的运行速度可能会变得很慢。 -In this article, we’ll look at some best practices for writing fast JavaScript programs. +在本文中,我们将介绍一些关于编写高性能 JavaScript 程序的最佳实践。 -## Reduce DOM Manipulation With Host Object and User’s Browser +## 通过宿主对象 (Host Object) 与用户的浏览器来减少 DOM 操作 -DOM manipulation is slow. The more we do, the slower it’ll be. Since DOM manipulation is synchronous, each action is done one at a time, holding the rest of our program. +DOM 操作是缓慢的。我们操作得越多,其速度就越慢。由于 DOM 操作是同步的,因此每个操作都是一次性完成的,同一时间程序的其它操作就被挂起了。 -Therefore, we should minimize the number of DOM manipulation actions that we're doing. +所以,我们应该尽量减少正在执行的 DOM 操作。 -The DOM can be blocked by loading CSS and JavaScript. However, images aren’t blocking render so that they don’t hold up our page from finishing loading. +加载 CSS 和 JavaScript 会阻塞 DOM。 不过,图像是不会阻塞渲染的,因此加载图像不会耽搁页面加载。 -However, we still want to minimize the size of our images. +但是,我们仍然希望能尽可能降低图像的大小。 -Render blocking JavaScript code can be detected with Google PageSpeed Insights, which tells us how many pieces of render-blocking JavaScript code we have. +可以通过 Google 的网页加载速度检测工具 (Google PageSpeed Insights) 检测阻塞渲染的 JavaScript 代码,它会告诉我们有多少段阻塞渲染的 JavaScript 代码。 -Any inline CSS would block the rendering of the whole page. They are the styles that are scattered within our page with `style` attributes. +任何内联的 CSS 都会阻塞整个页面的渲染。它们是分散在页面中,使用 `style` 属性的样式。 -We should move them all to their own style sheets, inside the `style` tag, and below the body element. +我们应该将它们全部移动到其所属的样式表中,放在 `style` 标签内,并放在 body 元素下方。 -CSS should be concatenated and minified to reduce the number of the stylesheet to load and their size. +为了减少要加载的样式表数量及其大小,CSS 应该进行合并和压缩。 -We can also mark `link` tags as non-render blocking by using media queries. For instance, we can write the following to do that: +我们也能通过媒体查询将 `link` 标签标记为不渲染的部分。例如,我们能通过编写以下代码来实现: ```html ``` -so that it only loads when the page is displayed in portrait orientation. +这样它就只会在页面纵向显示的时候被加载。 -We should move style manipulation outside of our JavaScript and put styles inside our CSS by putting styles within their own class in a stylesheet file. +我们应该将样式操作移到 JavaScript 以外,并通过将样式放入样式表文件内它们所属的类中,以达到将样式放入 CSS 中的目的。 -For instance, we can write the following code to add a class in our CSS file: +例如,我们可以编写以下代码,以在 CSS 文件中添加一个类: ```css .highlight { @@ -49,62 +49,62 @@ For instance, we can write the following code to add a class in our CSS file: } ``` -and then we can add a class with the `classList` object as follows: +然后我们可以通过 `classList` 对象添加一个类,如下所示: ```js const p = document.querySelector('p'); p.classList.add('highlight'); ``` -We set the p element DOM object to its own constant so we can cache it and reuse it anywhere and then we call `classList.add` to add the `hightlight` class to it. +我们将 `p` 元素的 DOM 对象设置为常量,这样就可以缓存它,以便在任何地方复用,然后调用 `classList.add` 方法来给它添加 `hightlight` 类。 -We can also remove it if we no longer want it. This way, we don’t have to do a lot of unnecessary DOM manipulation operations in our JavaScript code. +我们也可以在不使用的时候删掉它。这样,就不用在 JavaScript 代码中进行很多不必要的 DOM 操作了。 -If we have scripts that no other script depends on, we can load then asynchronously so that they don’t block the loading of other scripts. +如果我们的脚本没有依赖其它脚本,则可以异步地加载它们,这样它们就不会阻塞其它脚本的加载了。 -We just put `async` in our script tag so that we can load our script asynchronously as follows: +我们只需将 `async` 放进我们的脚本标签中,就可以实现异步加载了,如下所示: ```html ``` -Now `script.js` will load in the background instead of in the foreground. +现在 `script.js` 将能够异步加载。 -We can also defer the loading of scripts by using the `defer` directive. However, it guarantees that the script in the order they were specified on the page. +我们也可以通过 `defer` 指令来延迟脚本的加载。它还保证了脚本会按照页面上出现的顺序执行。 -This is a better choice if we want our scripts to load one after another without blocking the loading of other things. +如果希望我们的脚本按顺序地加载,并且不阻塞其它加载,这是一个更好的选择。 -Minifying scripts is also a must-do task before putting our code into production. To do that, we use module bundlers like Webpack and Parcel, which so create a project and then build them for us automatically. +在将我们的代码投入生产之前,压缩脚本也是一项必做的工作。为此,我们使用像 Webpack 和 Parcel 这样的模块化打包工具,它会为我们自动地创建并打包项目。 -Also, command-line tools for frameworks like Vue and Angular also do code minification automatically. +同样的,Vue 和 Angular 框架的命令行工具也会自动进行压缩代码的工作。 -## Minimize the Number of Dependencies Our App Uses +## 最小化我们的应用程序使用的依赖数量 -We should minimize the number of scripts and libraries that we use. Unused dependencies should also be removed. +我们应该尽量最小化所使用的脚本和库,无用的依赖应该删除掉。 -For instance, if we’re using Lodash methods for array manipulation, then we can replace them with native JavaScript array methods, which are just as good. +例如,如果我们为了操作数组而使用 Lodash 库的方法,那么我们可以使用原生的 JavaScript 数组方法替换之,效果同样好。 -Once we remove our dependency, we should remove them from `package.json` and the run `npm prune` to remove the dependency from our system. +一旦删除依赖,我们同时应该从 `package.json` 中将其删除,并且运行 `npm prune` 命令,将这些依赖从我们的系统中清除。 ![Photo by [Tim Carey](https://unsplash.com/@baudy?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)](https://cdn-images-1.medium.com/max/6154/0*9Qx9V9XpyjsjvSME) -## Poor Event Handling +## 糟糕的事件处理 -Event handling code is always slow if they’re complex. We can improve performance by reducing the depth of our call stack. +复杂的事件处理代码总是很慢。我们可以通过减少调用栈的深度来提高性能。 -That means we call as few functions as possible. Put everything in CSS style sheets if possible if we’re manipulating styles in our event handlers. +这意味着我们将尽可能地少调用方法。如果我们在事件处理中操作样式的话,尽量将样式操作放入 CSS 样式表中。 -And do everything to reduce calling functions like using the `**` operator instead of calling `Math.pow` . +尽量少调用方法,例如使用 `**` 操作符来代替 `Math.pow` 方法。 -## Conclusion +## 结论 -We should reduce the number of dependencies and loading them in an async manner if possible. +我们应该尽可能减少依赖的数量,并异步地加载它们。 -Also, we should reduce the CSS in our code and move them to their own stylesheets. +另外,我们应该减少代码中的 CSS,并将它们挪到各自所属的样式表中。 -We can also add media queries so that stylesheets don’t load everywhere. +我们还可以添加媒体查询,以便样式表不会在每种设备都加载。 -Finally, we should reduce the number of functions that are called in our code. +最后,我们应该减少代码中方法被调用的次数。 > 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 diff --git a/TODO1/simple-mailer-with-django.md b/TODO1/simple-mailer-with-django.md new file mode 100644 index 00000000000..d9ad09392d4 --- /dev/null +++ b/TODO1/simple-mailer-with-django.md @@ -0,0 +1,417 @@ +> * 原文地址:[Build a Simple Mailing Service with Django](https://medium.com/python-in-plain-english/simple-mailer-with-django-2a7e2ad34b34) +> * 原文作者:[Juli Colombo](https://medium.com/@julietanataliacolombo) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/simple-mailer-with-django.md](https://github.com/xitu/gold-miner/blob/master/TODO1/simple-mailer-with-django.md) +> * 译者: +> * 校对者: + +# Build a Simple Mailing Service with Django + +![](https://cdn-images-1.medium.com/max/3840/1*vXc5t2OrAan1o9viF-MkBQ.png) + +When creating a web application, it is frequently requested to send emails: no matter if it has to be done when users sign up in our platform, or if they forget their password, or if a payment confirmation has to be sent after a purchase. The email requirement is actually very important and it can be messy if we don’t structure an email service from the beginning. + +This post aims not only to help you define a flexible email service from scratch that allows you to change the email delivery platform easily, but also to be supple in terms of the content of the actual email. + +I will be using [Django](https://www.djangoproject.com/) to show an example this time, but I hope the main idea can be spread to other frameworks and programming languages you might be using. + +Let’s start! + +## Sending a basic email + +Suppose we want to send an email to a user after he or she signs up to our web application. We can use [Django’s documentation](https://docs.djangoproject.com/en/3.0/topics/email/) to send that email in the view after all validations were run and the user was created. We will come up with something like this: + +```Python +import logging + +from rest_framework.views import APIView +from django.http import JsonResponse +from django.core.mail import send_mail + +from users.models import User + +logger = logging.getLogger('django') + + +class RegisterView(APIView): + + def post(self, request): + # Run validations + if not request.data: + return JsonResponse({'errors': 'User data must be provided'}, status=400) + if User.objects.filter(email=request.data['email']).exists(): + return JsonResponse({'errors': 'Email already in use'}, status=400) + try: + # Create new user + user = User.objects.create_user(email=request.data['email'].lower()) + user.set_password(request.data['password']) + user.save() + + # Send welcome email + send_mail( + subject='Welcome!', + message='Hey there! Welcome to our platform.', + html_message='

Het there! Welcome to our platform.

' + from_email='from@example.com', + recipient_list=[user.email], + fail_silently=False, + ) + + return JsonResponse({'status': 'ok'}) + except Exception as e: + logger.error('Error at %s', 'register view', exc_info=e) + return JsonResponse({'errors': 'Wrong data provided'}, status=400) +``` + +Of course you must set up some important configurations in your settings like EMAIL_HOST and EMAIL_PORT, as the documentation says. + +Great! We are already sending our welcome email! + +## Create a mailer + +As I said before, we will probably need to send emails in several parts of our application, so it would be nice to have an email service or mailer who handles all the email requests, making it easier to add fixes or changes, because we won’t have to search through the whole code. + +```Python +import logging + +from django.conf import settings +from django.core.mail import send_mail + +from users.models import User + +logger = logging.getLogger('django') + + +class BaseMailer(): + def __init__(self, to_email, subject, message, html_message): + self.to_email = to_email + self.subject = subject + self.message = message + self.html_message = html_message + + def send_email(self): + send_mail( + subject=self.subject, + message=self.message, + html_message=self.html_message, + from_email='from@example.com', + recipient_list=[self.to_email], + fail_silently=False, + ) +``` + +Let’s see how our register view will look like after this change: + +```Python +import logging + +from rest_framework.views import APIView +from django.http import JsonResponse +from django.core.mail import send_mail + +from users.models import User +from users.mailers import BasicMailer + +logger = logging.getLogger('django') + + +class RegisterView(APIView): + + def post(self, request): + # Run validations + if not request.data: + return JsonResponse({'errors': 'User data must be provided'}, status=400) + if User.objects.filter(email=request.data['email']).exists(): + return JsonResponse({'errors': 'Email already in use'}, status=400) + try: + # Create new user + user = User.objects.create_user(email=request.data['email'].lower()) + user.set_password(request.data['password']) + user.save() + + # Send welcome email + BasicMailer(to_email=user.email, + subject='Welcome!', + message='Hey there! Welcome to our platform.', + html_message='

Het there! Welcome to our platform.

').send_email() + + return JsonResponse({'status': 'ok'}) + except Exception as e: + logger.error('Error at %s', 'register view', exc_info=e) + return JsonResponse({'errors': 'Wrong data provided'}, status=400) +``` + +## Subclass mailers + +Now that we have moved all the “email code” to a single place, we can take advantage of that! How about creating specific mailers that know what content to send when they are called? Let’s add a mailer that will get into action everytime new users sign up and another one that sends orders’ confirmations! + +```Python +import logging + +from django.conf import settings +from django.core.mail import send_mail + +from users.models import User + +logger = logging.getLogger('django') + + +class BaseMailer(): + def __init__(self, to_email, subject, message, html_message): + self.to_email = to_email + self.subject = subject + self.message = message + self.html_message = html_message + + def send_email(self): + send_mail( + subject=self.subject, + message=self.message, + html_message=self.html_message, + from_email='from@example.com', + recipient_list=[self.to_email], + fail_silently=False, + ) + + +class RegisterMailer(BaseMailer): + def __init__(self, to_email): + super().__init__(to_email, + subject='Welcome!', + message='Hey there! Welcome to our platform.', + html_message='

Het there! Welcome to our platform.

') + + +class NewOrderMailer(BaseMailer): + def __init__(self, to_email): + super().__init__(to_email, + subject='New Order', + message='You have just created a new order', + html_message='

You have just created a new order.

') +``` + +This shows that it is very easy to incorporate more mailers for different situations. You just have to let the basic mailer handle the implementation, and the subclasses set the content. + +Now our register view will look much cleaner, because it doesn’t have to handle all the email stuff: + +```Python +import logging + +from rest_framework.views import APIView +from django.http import JsonResponse +from django.core.mail import send_mail + +from users.models import User +from users.mailers import RegisterMailer + +logger = logging.getLogger('django') + + +class RegisterView(APIView): + + def post(self, request): + # Run validations + if not request.data: + return JsonResponse({'errors': 'User data must be provided'}, status=400) + if User.objects.filter(email=request.data['email']).exists(): + return JsonResponse({'errors': 'Email already in use'}, status=400) + try: + # Create new user + user = User.objects.create_user(email=request.data['email'].lower()) + user.set_password(request.data['password']) + user.save() + + # Send welcome email + RegisterMailer(to_email=user.email).send_email() + + return JsonResponse({'status': 'ok'}) + except Exception as e: + logger.error('Error at %s', 'register view', exc_info=e) + return JsonResponse({'errors': 'Wrong data provided'}, status=400) +``` + +## Using Sendgrid + +Suppose that we have to move our email backend to [Sendgrid](https://sendgrid.com/) (a customer communication platform for transactional and marketing email) using its official [python library](https://github.com/sendgrid/sendgrid-python). We won’t be using Django’s **send_email** any longer. Instead we will have to use the new library’s syntax. Well… We are lucky! We have all the code related to email management in just one place and it will be easier for us to do this refactor 😉 + +```Python +import logging + +from django.conf import settings +from sendgrid import SendGridAPIClient, Email, Personalization +from sendgrid.helpers.mail import Mail + +from users.models import User + +logger = logging.getLogger('django') + + +class BaseMailer(): + def __init__(self, email, subject, template_id): + self.mail = Mail() + self.subject = subject + self.template_id = template_id + + def create_email(self): + self.mail.from_email = Email(settings.FROM_EMAIL) + self.mail.subject = self.subject + self.mail.template_id = self.template_id + personalization = Personalization() + personalization.add_to(Email(self.user.email)) + self.mail.add_personalization(personalization) + + def send_email(self): + self.create_email() + try: + sg = SendGridAPIClient(settings.SENDGRID_API_KEY) + sg.send(self.mail) + except Exception as e: + logger.error('Error at %s', 'mailer', exc_info=e) + + +class RegisterMailer(BaseMailer): + def __init__(self, to_email): + super().__init__(to_email, subject='Welcome!', template_id=1234) + + +class NewOrderMailer(BaseMailer): + def __init__(self, to_email): + super().__init__(to_email, subject='New Order', template_id=5678) +``` + +Be aware that you must set Sendgrid’s api key in your settings and also note that html templates will be managed directly from Sendrid’s web page, and you will need to have the templates’ ids to specify which one should be used. + +Great! It was not so difficult and we don’t have to modify each line of code where we want to send an email! + +Let’s get a little bit further now. + +## Personalize email’s content with domain data + +Of course when we send emails, it is likely to use some domain data to fill in the email’s template. For example, it would be friendlier if our welcome email had the new user’s name in it. Sendgrid allows you to define variables in the template that will be replaced with the actual information that receives from us. So let’s add this data! + +```Python +import logging + +from django.conf import settings +from sendgrid import SendGridAPIClient, Email, Personalization +from sendgrid.helpers.mail import Mail + +from users.models import User + +logger = logging.getLogger('django') + + +class BaseMailer(): + def __init__(self, email, subject, template_id): + self.mail = Mail() + self.user = User.objects.get(email=email) + self.subject = subject + self.template_id = template_id + self.substitutions = { + 'user_name': self.user.first_name, + 'user_surname': self.user.last_name + } + + + def create_email(self): + self.mail.from_email = Email(settings.FROM_EMAIL) + self.mail.subject = self.subject + self.mail.template_id = self.template_id + personalization = Personalization() + personalization.add_to(Email(self.user.email)) + personalization.dynamic_template_data = self.substitutions + self.mail.add_personalization(personalization) + + def send_email(self): + self.create_email() + try: + sg = SendGridAPIClient(settings.SENDGRID_API_KEY) + sg.send(self.mail) + except Exception as e: + logger.error('Error at %s', 'mailer', exc_info=e) + + +class RegisterMailer(BaseMailer): + def __init__(self, to_email): + super().__init__(to_email, subject='Welcome!', template_id=1234) + + +class NewOrderMailer(BaseMailer): + def __init__(self, to_email): + super().__init__(to_email, subject='New Order', template_id=5678) +``` + +The only problem I see here is that this substitutions scheme isn’t very flexible. It is likely to happen that we have to pass other data depending on the context that cannot be accessed via the user, for instance: new order’s number, reset password link, etc. There can be quite a lot of these variables and passing them as named parameters could make the code messier and dirtier. We want a keyworded, variable-length argument list, usually called ****kwargs, **but let’s name them** **substitutions** to make it more expressive: + +```Python +import logging + +from django.conf import settings +from sendgrid import SendGridAPIClient, Email, Personalization +from sendgrid.helpers.mail import Mail + +from users.models import User + +logger = logging.getLogger('django') + + +class BaseMailer(): + def __init__(self, email, subject, template_id, **substitutions): + self.mail = Mail() + self.user = User.objects.get(email=email) + self.subject = subject + self.template_id = template_id + self.substitutions = { + 'user_name': self.user.first_name, + 'user_surname': self.user.last_name + } + + for key in substitutions: + self.substitutions.update({key: substitutions[key]}) + + def create_email(self): + self.mail.from_email = Email(settings.FROM_EMAIL) + self.mail.subject = self.subject + self.mail.template_id = self.template_id + personalization = Personalization() + personalization.add_to(Email(self.user.email)) + personalization.dynamic_template_data = self.substitutions + self.mail.add_personalization(personalization) + + def send_email(self): + self.create_email() + try: + sg = SendGridAPIClient(settings.SENDGRID_API_KEY) + sg.send(self.mail) + except Exception as e: + logger.error('Error at %s', 'mailer', exc_info=e) + + +class RegisterMailer(BaseMailer): + def __init__(self, to_email, **substitutions): + super().__init__(to_email, subject='Welcome!', template_id=1234, **substitutions) + + +class NewOrderMailer(BaseMailer): + def __init__(self, to_email): + super().__init__(to_email, subject='New Order', template_id=5678, **substitutions) +``` + +In case you have to pass extra information to the mailer, you will do something like this: + +``` +NewOrderMailer(user.email, order_id=instance.id).send_email() +PasswordResetMailer(user.email, key=password_token.key).send_email() +``` + +## Summing up + +We have created a flexible mailer which encapsulates all the code related to emails in one place, making it easier to maintain, and that also receives variable context parameters to fill in the email content! Thinking of the whole approach at once could be difficult, but making it step by step is simpler and the benefits are a lot. **I encourage you to add the feature of attaching files to an email using this design!** + +Thank you very much for reading this post and I hope it helps you for your projects. Follow me for upcoming posts, and I wish you good luck and happy coding! + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[区块链](https://github.com/xitu/gold-miner#区块链)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/swiftui-3d-scroll-effect.md b/TODO1/swiftui-3d-scroll-effect.md index 59364454984..50d0abdb7ee 100644 --- a/TODO1/swiftui-3d-scroll-effect.md +++ b/TODO1/swiftui-3d-scroll-effect.md @@ -2,18 +2,18 @@ > * 原文作者:[Jean-Marc Boullianne](https://medium.com/@jboullianne) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/swiftui-3d-scroll-effect.md](https://github.com/xitu/gold-miner/blob/master/TODO1/swiftui-3d-scroll-effect.md) -> * 译者: -> * 校对者: +> * 译者:[chaingangway](https://github.com/chaingangway) +> * 校对者:[lsvih](https://github.com/lsvih) -# SwiftUI 3D Scroll Effect +# 用 SwiftUI 实现 3D Scroll 效果 ![Finished 3D Scroll Effect](https://cdn-images-1.medium.com/max/2000/0*pYnR4ym84WIZk3tf.gif) -Here’s a look at the kind of 3D scroll effect we’ll be making today. At the end of this tutorial, you’ll be able to add this 3D effect to any custom SwiftUI view in your app. Let’s get started! +我们预览下今天要实现的 3D scroll 效果。学完本教程后,你就可以在你的 App 中把这种 3D 效果加入任何自定义的 SwiftUI 视图。下面我们来开始本教程的学习。 -## Getting Started +## 入门 -Start by creating a new SwiftUI View. For example purposes, I’ll be showing a list of rectangles in different colors, so I named my view `ColorList`. +首先,创建一个新的 SwiftUI 视图。为了举例说明,在这个新视图中,我会展示一个有各种颜色的矩形列表,并把新视图命名为 `ColorList`。 ```swift import SwiftUI @@ -31,17 +31,17 @@ struct ColorList_Previews: PreviewProvider { } ``` -## Color Data +## 颜色数据 -At the top of your view struct, add a variable for keeping track of colors. +在视图的结构体里,添加一个用于记录颜色的变量。 ```swift var colors: [Colors] ``` -## Making the List +## 实现这个列表 -Inside your `body` variable, get rid of the placeholder `Text`. Add in a `HStack` wrapping in a `ScrollView` like this. +在 `body` 变量的内部,删除掉占位 `Text`。在 `ScrollView` 嵌套中添加一个 `HStack`,如下: ```swift var body: some View { @@ -53,9 +53,9 @@ var body: some View { } ``` -## Show the Rectangles +## 展示矩形 -Inside your `HStack` we need to show a `Rectangle` for each color stored in `colors`. For this we'll use a `ForEach`. I've gone ahead and modified the frame for the rectangle to something more relatable to a traditional UI Card. +我们使用 `ForEach` 在 `HStack` 内部根据 `colors` 中的数据分别创建不同颜色的矩形。此外,我修改了矩形的 frame,让它看起来与传统 UI 布局更像一些。 ```swift var body: some View { @@ -71,7 +71,7 @@ var body: some View { } ``` -And if you go ahead and provide the preview struct with a list of colors like this: +在 Preview 结构体中传入如下的颜色参数: ```swift struct ColorList_Previews: PreviewProvider { @@ -81,13 +81,13 @@ struct ColorList_Previews: PreviewProvider { } ``` -You should see this! +你可以看到下图中的效果: ![](https://cdn-images-1.medium.com/max/2000/0*NfpStvbJHfMO2Tqq.png) -## Adding the 3D Effect +## 增加 3D 效果 -Start by wrapping your `Rectangle` in a `GeometryReader`. This will allow us to grab a reference to the frame of the `Rectangle` as it moves across the screen. +首先,把 `Rectangle` 嵌套在 `GeometryReader` 中。这样的话,当 `Rectangle` 在屏幕上移动的时候,我们就可以获得其 frame 的引用。 ```swift var body: some View { @@ -105,27 +105,27 @@ var body: some View { } ``` -You will need to change the `HStack` spacing you defined above, due to the way `GeometryReader` works. +根据 `GeometryReader` 的用法要求,我们需要修改上面定义的 `HStack` 的 spacing 属性。 -Then add this line to your `Rectangle` +在 `Rectangle` 中加入下面这行代码。 ```swift .rotation3DEffect(Angle(degrees: (Double(geometry.frame(in: .global).minX) - 210) / -20), axis: (x: 0, y: 1.0, z: 0)) ``` -The `Angle` you're passing into the function is changing as the `Rectangle` moves across the screen. Take a particular look at the `.frame(in:)` function. It allows you to grab the `CGRect` of the `Rectangle` and uses its `minX` coordinate for angle calculations. +当 `Rectangle` 在屏幕上移动时,这个方法的 `Angle` 参数会发生改变。请重点看 `.frame(in:)` 这个函数,你可以获取 `Rectangle` 的 `CGRect` 属性 `minX` 变量来计算角度。 -The `axis` parameter is a Tuple that details which axis to modify using the angle you just passed in. In this case it's the Y-axis. +`axis` 参数是一个元组类型,它定义了在使用你传入的角度参数时,哪一个坐标轴要发生改变。在本例中,是 Y 轴。 -> The documentation for the rotation3DEffect() can be found [here](https://developer.apple.com/documentation/swiftui/scrollview/3287538-rotation3deffect) on Apple’s Official Website. +> rotation3DEffect() 方法的文档可以在苹果官方网站的 [这里](https://developer.apple.com/documentation/swiftui/scrollview/3287538-rotation3deffect) 找到。 -If you go ahead and run the example you should see your `Rectangles` rotating as they move across the screen! +下一步,把这个案例跑起来。当矩形在屏幕上移动时,你可以看到它们在旋转。 -> I’ve also modified the corner radius of the rectangle as well as added a drop shadow to make it look a little better. +> 我还修改了矩形的 cornerRadius 属性,并加上了投影效果,让它更美观。 ![Pretty cool right!?](https://cdn-images-1.medium.com/max/2000/0*IidRWGBSe936-9Ls.gif) -## Final Product +## 最终效果 ```swift struct ColorList: View { @@ -151,9 +151,9 @@ struct ColorList: View { } ``` -## That’s all Folks! +## 结束 -If you enjoyed this post, please consider subscribing to my website using this [link](https://trailingclosure.com/signup/), and if you aren’t reading this on [TrailingClosure.com](https://trailingclosure.com/), please come check us out sometime! +如果您喜欢这篇文章,可以用这个 [链接](https://trailingclosure.com/signup/) 订阅我们的网站。如果您不是在 [TrailingClosure.com](https://trailingclosure.com/) 阅读本文,以后也可以来这个网站看看。 > 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 diff --git a/TODO1/swiftui-animating-color-changes.md b/TODO1/swiftui-animating-color-changes.md new file mode 100644 index 00000000000..bf78c12c189 --- /dev/null +++ b/TODO1/swiftui-animating-color-changes.md @@ -0,0 +1,498 @@ +> * 原文地址:[SwiftUI: Animating Color Changes](https://levelup.gitconnected.com/swiftui-animating-color-changes-6a87d237dcea) +> * 原文作者:[Jean-Marc Boullianne](https://medium.com/@jboullianne) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/swiftui-animating-color-changes.md](https://github.com/xitu/gold-miner/blob/master/TODO1/swiftui-animating-color-changes.md) +> * 译者: +> * 校对者: + +# SwiftUI: Animating Color Changes + +Time to spice up your color changes! You’re going to learn how to animate background color changes in SwiftUI using `Paths` and `AnimatableData`! + +![](https://cdn-images-1.medium.com/max/2000/0*ZiMbs5MNguBktfIt.gif) + +![](https://cdn-images-1.medium.com/max/2000/0*Wc0gTdaBCBHCYpYL.gif) + +What kind of color changing madness is this?! + +#### Getting Started + +The key to our background color changing magic is going to be creating our own custom SwiftUI `Shape` struct. We'll call it `SplashShape`. `Shape` structs utilize the function `path(in rect: CGRect) -> Path` to define what they look like. This is the function we'll be using to create the various animations. + +#### Create the SplashShape struct + +Go ahead and create a new `Shape` struct called `SplashStruct`. + +```swift +import SwiftUI + +struct SplashShape: Shape { + + func path(in rect: CGRect) -> Path { + return Path() + } +} +``` + +For starters we’ll be creating two animations. `leftToRight` and `rightToLeft` shown below. + +![`leftToRight` & `rightToLeft`](https://cdn-images-1.medium.com/max/2000/0*IhBr4_qhxe5FRnTN.gif) + +#### SplashAnimation + +We will create an `enum` called `SplashAnimation` for our custom animations. This will allow us to easily add more animations in the future (see the end for more!). + +```swift +import SwiftUI + +struct SplashShape: Shape { + + public enum SplashAnimation { + case leftToRight + case rightToleft + } + + func path(in rect: CGRect) -> Path { + return Path() + } +} +``` + +In our `path()` function we'll switch on which animation our shape is using and generate the required `Path` for the animation. But first, we must create variables to hold the animation type as well as the progress of the animation. + +```swift +import SwiftUI + +struct SplashShape: Shape { + + public enum SplashAnimation { + case leftToRight + case rightToleft + } + + var progress: CGFloat + var animationType: SplashAnimation + + func path(in rect: CGRect) -> Path { + return Path() + } +} +``` + +`progress` will be a value between `0` and `1`, which will detail how far we are through the animation of the color change. This will come in handy next when we write our `path()` function. + +#### Writing the path() function + +As said earlier we need to figure out what type of animation we’re using in order to return the correct `Path`. Start off by writing a `switch` statement in your `path()` function using the `animationType` defined earlier. + +```swift +func path(in rect: CGRect) -> Path { + switch animationType { + case .leftToRight: + return Path() + case .rightToLeft: + return Path() + } +} +``` + +As of right now, this will return empty paths. We need to actually create the functions to animate the paths. + +#### Create The Animation Functions + +Below your `path()` function, create two new functions called `leftToRight()` and `rightToLeft()` for each type of animation. Within each function, we will create a `Path` in the shape of a rectangle that will grow over time according to our `progress` variable. + +```swift +func leftToRight(rect: CGRect) -> Path { + var path = Path() + path.move(to: CGPoint(x: 0, y: 0)) // Top Left + path.addLine(to: CGPoint(x: rect.width * progress, y: 0)) // Top Right + path.addLine(to: CGPoint(x: rect.width * progress, y: rect.height)) // Bottom Right + path.addLine(to: CGPoint(x: 0, y: rect.height)) // Bottom Left + path.closeSubpath() // Close the Path + return path +} + +func rightToLeft(rect: CGRect) -> Path { + var path = Path() + path.move(to: CGPoint(x: rect.width, y: 0)) + path.addLine(to: CGPoint(x: rect.width - (rect.width * progress), y: 0)) + path.addLine(to: CGPoint(x: rect.width - (rect.width * progress), y: rect.height)) + path.addLine(to: CGPoint(x: rect.width, y: rect.height)) + path.closeSubpath() + return path +} +``` + +Then utilize the two new functions in your `path()` function above. + +```swift +func path(in rect: CGRect) -> Path { + switch animationType { + case .leftToRight: + return leftToRight(rect: rect) + case .rightToLeft: + return rightToLeft(rect: rect) + } +} +``` + +#### AnimatableData + +In order to make sure Swift knows how to animate our `Shape` when the `progress` variable is changed, we need to specify which variable is animating. Just below our `progress` and `animationType` variables, define `animatableData`. It's a variable based on the [`Animatable` protocol](https://developer.apple.com/documentation/swiftui/animatable) which helps SwiftUI know how to animate views when they change. + +```swift +var progress: CGFloat +var animationType: SplashAnimation + +var animatableData: CGFloat { + get { return progress } + set { self.progress = newValue} +} +``` + +![`SplashShape` animating as `progress` changes.](https://cdn-images-1.medium.com/max/2000/0*8vr8fNf-Fa86z6XF.gif) + +#### Animating the Color Change + +Up to now we’ve created a `Shape` which will animate over time. Next we need to add this shape to a view and automatically animate it when the view's color changes. This is where `SplashView` comes in to play. We're going to create a `SplashView` to automatically update the `progress` variable of `SplashShape`. When `SplashView` receives a new `Color` it triggers the animation. + +Get started by creating the `SplashView` struct. + +```swift +import SwiftUI + +struct SplashView: View { + + var body: some View { + // SplashShape Here + } + +} +``` + +Remember our `SplashShape` takes a `SplashAnimation` enum as a parameter so we'll need to add this as a parameter to our `SplashView`. In addition, we're animating the background color change of the view, so we'll need to take in a `Color` as well. Our initializer is detailed below. + +`ColorStore` is a custom ObservableObject. It is used to recieve `Color` updates in the `SplashView` struct, so that we can initiate the `SplashShape` animation, and ultimately the background color change. We'll show how that works in a second. + +```swift +struct SplashView: View { + + var animationType: SplashShape.SplashAnimation + @State private var prevColor: Color // Stores background color + @ObservedObject var colorStore: ColorStore // Send new color updates + + + init(animationType: SplashShape.SplashAnimation, color: Color) { + self.animationType = animationType + self._prevColor = State(initialValue: color) + self.colorStore = ColorStore(color: color) + } + + var body: some View { + // SplashShape Here + } + +} + +class ColorStore: ObservableObject { + @Published var color: Color + + init(color: Color) { + self.color = color + } +} +``` + +#### Creating the SplashView body + +Inside the `body` variable we need to return a `Rectangle` set to the current color of the `SplashView`. Then using the `ColorStore` Obseravble Object defined earlier, we can receive color updates to animate our view. + +```swift +var body: some View { + Rectangle() + .foregroundColor(self.prevColor) // Current Color + .onReceive(self.colorStore.$color) { color in + // Animate Color Update Here + } +} +``` + +When changing the colors, we need some way to keep track of the color that the `SplashView` is being changed to, as well as the progress. We'll define the `layers` variable in order to do this. + +``` +@State var layers: [(Color,CGFloat)] = [] // New Color & Progress +``` + +Now back inside our `body` variable we need to add the newly received `Colors` to the `layers` variable. When we add them, we set the progress to `0` since they were just added. Then, over the course of half a second we animate their progress to `1`. + +```swift +var body: some View { + Rectangle() + .foregroundColor(self.prevColor) // Current Color + .onReceive(self.colorStore.$color) { color in + // Animate Color Update Here + self.layers.append((color, 0)) + + withAnimation(.easeInOut(duration: 0.5)) { + self.layers[self.layers.count-1].1 = 1.0 + } + } +} +``` + +As of right now, this will add the new colors to the` layers` variable, but they aren't displayed on top of the `SplashView`. To do this we need to display each layer as an overlay on the `Rectangle` inside the `body` variable. + +```swift +var body: some View { + Rectangle() + .foregroundColor(self.prevColor) + .overlay( + ZStack { + ForEach(layers.indices, id: \.self) { x in + SplashShape(progress: self.layers[x].1, animationType: self.animationType) + .foregroundColor(self.layers[x].0) + } + + } + + , alignment: .leading) + .onReceive(self.colorStore.$color) { color in + // Animate color update here + self.layers.append((color, 0)) + + withAnimation(.easeInOut(duration: 0.5)) { + self.layers[self.layers.count-1].1 = 1.0 + } + } +} +``` + +#### Give it a Run + +Using the code below you can give it a run inside an emulator. What this does, is when the button inside `ContentView` is pressed, it advances the `index` used to select the `SplashView` color, which triggers an update to the `ColorStore` inside. This make the `SplashView` add a `SplashShape` layer and animate it's addition to the view. + +```swift +import SwiftUI + +struct ContentView: View { + var colors: [Color] = [.blue, .red, .green, .orange] + @State var index: Int = 0 + + @State var progress: CGFloat = 0 + var body: some View { + VStack { + + SplashView(animationType: .leftToRight, color: self.colors[self.index]) + .frame(width: 200, height: 100, alignment: .center) + .cornerRadius(10) + .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 4) + + Button(action: { + self.index = (self.index + 1) % self.colors.count + }) { + Text("Change Color") + } + .padding(.top, 20) + } + + } +} +``` + +![Color Changing Goodness!](https://cdn-images-1.medium.com/max/2000/0*0cplu29bi4dyHzkt.gif) + +#### Not Finished Yet! + +We’re missing one thing. AS of right now, we’re continuously adding layer upon layer to our `SplashView`. We need to make sure we delete those layers after they finish animating and are displayed. + +Inside your `onReceive()` function inside your `SplashView` struct's `body` variable, make the following change: + +```swift +.onReceive(self.colorStore.$color) { color in + self.layers.append((color, 0)) + + withAnimation(.easeInOut(duration: 0.5)) { + self.layers[self.layers.count-1].1 = 1.0 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.prevColor = self.layers[0].0 // Finalizes background color of SplashView + self.layers.remove(at: 0) // removes itself from layers array + } + } +} +``` + +This makes sure we delete old entries to the `layers` array and that our `SplashView` is showing the correct background color based on the latest update. + +#### Show us What You Made! + +Have you completed one of my tutorials? Send me a screenshot or drop me a link to the project. [TrailingClosure.com](https://trailingclosure.com) will be featuring user’s work! Find us on Twitter [@TrailingClosure](https://twitter.com/TrailingClosure), or email us at [howdy@TrailingClosure.com](mailto:howdy@trailingclosure.com) + +![](https://cdn-images-1.medium.com/max/2000/0*vuSb0VxT6pvGpp9z.gif) + +#### Source Code Posted on GitHub + +Checkout the [source code](https://github.com/jboullianne/SplashView) for this tutorial on my Github! Full source code for both `SplashShape` and `SplashView` are included, in addition to the examples shown. .... But wait, there's more! + +#### EXTRA CREDIT! + +If you’re familiar with my previous tutorials, you know I love extra credit 😉. I said in the beginning there would be more animations. This is the moment you’ve been waiting for…. **drum roll**…. + +## SplashAnimation 🥳 + +Wooohoo!! Remember this? I told you we’d come back to it to add more animation types. + +```swift +enum SplashAnimation { + case leftToRight + case rightToLeft + case topToBottom + case bottomToTop + case angle(Angle) + case circle +} + +func path(in rect: CGRect) -> Path { + + switch self.animationType { + case .leftToRight: + return leftToRight(rect: rect) + case .rightToLeft: + return rightToLeft(rect: rect) + case .topToBottom: + return topToBottom(rect: rect) + case .bottomToTop: + return bottomToTop(rect: rect) + case .angle(let splashAngle): + return angle(rect: rect, angle: splashAngle) + case .circle: + return circle(rect: rect) + } + +} +``` + +I know what you’re thinking… **“Whoa, that’s a lot of extra credit…”**. Don’t fret. By modifying our `path()` function inside `SplashShape`, and creating just a few more functions, we'll be animating like no one's business. + +Let’s take this animation by animation… + +#### topToBottom & bottomToTop + +Much like `leftToRight` and `rightToLeft` these functions create a path which starts from the bottom or top of the shape and grows over time using the `progress` variable. + +```swift +func topToBottom(rect: CGRect) -> Path { + var path = Path() + path.move(to: CGPoint(x: 0, y: 0)) + path.addLine(to: CGPoint(x: rect.width, y: 0)) + path.addLine(to: CGPoint(x: rect.width, y: rect.height * progress)) + path.addLine(to: CGPoint(x: 0, y: rect.height * progress)) + path.closeSubpath() + return path +} + +func bottomToTop(rect: CGRect) -> Path { + var path = Path() + path.move(to: CGPoint(x: 0, y: rect.height)) + path.addLine(to: CGPoint(x: rect.width, y: rect.height)) + path.addLine(to: CGPoint(x: rect.width, y: rect.height - (rect.height * progress))) + path.addLine(to: CGPoint(x: 0, y: rect.height - (rect.height * progress))) + path.closeSubpath() + return path +} +``` + +![](https://cdn-images-1.medium.com/max/2000/0*R5UO4dzgtvlUUjtC.gif) + +#### circle + +If you remember some of your grade school geometry, you’ll know the Pythagorean Theorem. `a^2 + b^2 = c^2` + +![`c` is the radius of the final circle our path needs to draw](https://cdn-images-1.medium.com/max/2100/0*taOHhdEX-GycqkbL.png) + +`a` and `b` can be considered our rectangle's `height` and `width`. This allows us to solve for `c`, the radius of the circle that is required to cover the entirety of the rectangle. We can build the circular path from this and grow it over time using the `progress` variable. + +```swift +func circle(rect: CGRect) -> Path { + let a: CGFloat = rect.height / 2.0 + let b: CGFloat = rect.width / 2.0 + + let c = pow(pow(a, 2) + pow(b, 2), 0.5) // a^2 + b^2 = c^2 --> Solved for 'c' + // c = radius of final circle + + let radius = c * progress + // Build Circle Path + var path = Path() + path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: radius, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 360), clockwise: true) + return path + +} +``` + +![Animating Using a Circle Path](https://cdn-images-1.medium.com/max/2000/0*a4qFyDe5jvcD-B1J.gif) + +#### angle + +This one is a little more involved. You need to calculate the slope of the angle using the tangent. Then create a line with the given slope. You’ll plot this line as a right triangle as you move the line across the rectangle. See the picture below. The various colored lines represent the line moving over time to cover the entire rectangle. + +![The line moves in order from the red, blue, green, then purple. to cover the rectangle](https://cdn-images-1.medium.com/max/2000/0*ogi8WYEI-T3-GsWh.png) + +The function is as follows: + +```swift +func angle(rect: CGRect, angle: Angle) -> Path { + + var cAngle = Angle(degrees: angle.degrees.truncatingRemainder(dividingBy: 90)) + + // Return Path Using Other Animations (topToBottom, leftToRight, etc) if angle is 0, 90, 180, 270 + if angle.degrees == 0 || cAngle.degrees == 0 { return leftToRight(rect: rect)} + else if angle.degrees == 90 || cAngle.degrees == 90 { return topToBottom(rect: rect)} + else if angle.degrees == 180 || cAngle.degrees == 180 { return rightToLeft(rect: rect)} + else if angle.degrees == 270 || cAngle.degrees == 270 { return bottomToTop(rect: rect)} + + + // Calculate Slope of Line and inverse slope + let m = CGFloat(tan(cAngle.radians)) + let m_1 = pow(m, -1) * -1 + let h = rect.height + let w = rect.width + + // tan (angle) = slope of line + // y = mx + b ---> b = y - mx ~ 'b' = y intercept + let b = h - (m_1 * w) // b = y - (m * x) + + // X and Y coordinate calculation + var x = b * m * progress + var y = b * progress + + // Triangle Offset Calculation + let xOffset = (angle.degrees > 90 && angle.degrees < 270) ? rect.width : 0 + let yOffset = (angle.degrees > 180 && angle.degrees < 360) ? rect.height : 0 + + // Modify which side the triangle is drawn from depending on the angle + if angle.degrees > 90 && angle.degrees < 180 { x *= -1 } + else if angle.degrees > 180 && angle.degrees < 270 { x *= -1; y *= -1 } + else if angle.degrees > 270 && angle.degrees < 360 { y *= -1 } + + // Build Triangle Path + var path = Path() + path.move(to: CGPoint(x: xOffset, y: yOffset)) + path.addLine(to: CGPoint(x: xOffset + x, y: yOffset)) + path.addLine(to: CGPoint(x: xOffset, y: yOffset + y)) + path.closeSubpath() + return path + +} +``` + +![Angles 45°, 135°, 225°, 315°](https://cdn-images-1.medium.com/max/2000/0*gxQtfGVNpOr50amB.gif) + +#### Support Future Tutorials Like This One! + +Please consider subscribing using this [link](https://trailingclosure.com/signup/). If you aren’t reading this on [TrailingClosure.com](https://trailingclosure.com/), please come check us out sometime! + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[区块链](https://github.com/xitu/gold-miner#区块链)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/the-importance-of-why-docs.md b/TODO1/the-importance-of-why-docs.md index bb37f23ac08..8805c228551 100644 --- a/TODO1/the-importance-of-why-docs.md +++ b/TODO1/the-importance-of-why-docs.md @@ -2,107 +2,107 @@ > * 原文作者:[Bytebase](https://medium.com/@bytebase) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/the-importance-of-why-docs.md](https://github.com/xitu/gold-miner/blob/master/TODO1/the-importance-of-why-docs.md) -> * 译者: -> * 校对者: +> * 译者:[Roc](https://github.com/QinRoc) +> * 校对者:[icy](https://github.com/Raoul1996),[司徒公子](https://github.com/todaycoder001) -# The Importance of “Why” Docs +# “为什么”文档的重要性 -> Giving future engineers context on why decisions were made +> 帮助未来的工程师们了解决定的来龙去脉 ![Photo by [Emily Morter](https://unsplash.com/@emilymorter?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/why?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)](https://cdn-images-1.medium.com/max/10368/1*2KhDOt8Dgcq17b-8rlMsig.jpeg) -Many of us have, at one time or another, blindly followed a pattern we noticed, thinking that must be the way to do it. We do so without questioning if that pattern is the best fit for our particular situation or if that pattern was ever a good idea to begin with. +我们中的许多人曾经盲目地遵循我们所被告知的模式,认为那一定是做这件事情的正确方式。我们就这样照做了,而没有怀疑那个模式对于我们所处的特殊情形是不是最佳选择,或者那个模式从一开始就不够好。 -In doing this, we rob ourselves of the opportunity to learn and deepen our understanding, to be intentional with our work, and, ultimately, to get better at our craft. Even more, we set yet another precedent for colleagues to do the same, instead of encouraging them to dig deeper. +在我们盲目照做的时候,我们放弃了学习和深化理解、专心工作以及最终提高技术的机会。甚至我们为其他同事树立了另一个照做的先例,而不是鼓励他们深入钻研。 -## The Girl and the Fish +## 女孩和鱼 ![](https://cdn-images-1.medium.com/max/10944/1*mvAQ0v229MXNdrTWSglD1w.jpeg) -I recently learned about the fable of a young girl watching her mother cook fish: +我最近听说了一则关于一个小女孩儿观察她的妈妈烤鱼的寓言: -> A little girl was watching her mother prepare a fish for dinner. Her mother cut the head and tail off the fish and then placed it into a baking pan. +> 一个小女孩儿正看着她的妈妈为晚餐做一条烤鱼。她的妈妈切除了鱼的头和尾巴,然后把剩下的部分放到了烤盘里。 > -> The little girl asked her mother why she cut the head and tail off the fish. +> 这个小女孩就问她的妈妈:“为什么你要切下鱼的头和尾巴呢?” > -> Her mother thought for a while and then said, “I’ve always done it that way. That’s how grandma always did it.” +> 她的妈妈想了一会儿,然后说:“我一直是这样做的呀。你的奶奶也是一直这么做的。” > -> Not satisfied with the answer, the little girl went to visit her grandma to find out why she cut the head and tail off the fish before baking it. Grandma thought for a while and replied, “I don’t know. My mother always did it that way.” +> 小女孩对这个答案不满意,于是就去拜访了奶奶,想找出她在烤鱼前切掉鱼的头和尾巴的原因。奶奶想了一会儿,然后回答道:“我也不知道。我的妈妈一直是这么做的。” > -> So the little girl and the grandma went to visit great-grandma to find ask if she knew the answer. Great-grandma thought for a while and said, “Because my baking pan was too small to fit in the whole fish!” +> 于是小女孩和她的奶奶就去拜访了曾祖母,询问她是否知道这个答案。曾祖母想了一会儿,然后说:“因为我的烤盘太小了,放不下整条鱼。” -**— [via Ptex Group](https://ptexgroup.com/learned-story-fish/)** +**——[ 来自 Ptex Group](https://ptexgroup.com/learned-story-fish/)** -The girl’s mother took off the head and tail of the fish because she was unaware of the great grandmother’s constraint of a small pan. She didn’t ask “why” when she adopted the recipe, and the great grandmother didn’t realize she should tell her. As a result, the girl’s mother was still able to cook fish, but she was cooking suboptimally. She was cooking uninformed. +女孩的妈妈切下了鱼的头和尾巴,因为她没有意识到小烤盘对曾祖母的约束才是需要这样做的原因。当她采用这个菜谱的时候,没有问“为什么”。曾祖母也没有意识到应该告诉她这样做的原因。结果,这个女孩儿的妈妈虽然也能够做鱼,但是她做鱼的方式不是最优的。她是在不了解的情况下做的鱼。 -By asking “why,” the little girl can change the recipe with confidence because she knows the original constraint of a small pan no longer holds. +通过问“为什么”,这个小女孩儿可以自信地改变菜谱,因为她知道现在已经没有了当初小烤盘的约束。 --- -## Asking “Why” in Docs +## 在文档中问“为什么” -Like the little girl, we want to break the cycle of doing without understanding. We can break this cycle by asking “why,” and by documenting why as we build. +像这个小女孩一样,我们也想打破在不理解的情况下就做事的循环。为了实现这个目标,我们可以问“为什么”,然后把问题和答案记录在文档里。 -Code tells you **how** a software system works. Docs tell you **why** a system works this way. +代码可以告诉你一个软件系统是**如何**工作的。文档则告诉你这个系统**为什么**以这种方式工作。 -> Code tells what, docs tell you why.” — **Steve Konves at [Hacker Noon](https://hackernoon.com/write-good-documentation-6caffb9082b4)** +> “代码告诉你做什么,文档告诉你为什么。” —— **Steve Konves 在 [Hacker Noon](https://hackernoon.com/write-good-documentation-6caffb9082b4) 写道。** -Writing down “why” as we build will: +在我们编程的时候写下“为什么”可以: -* Reduce the number of outages caused by changing code without understanding it -* Reduce the time spent hunting down why software was written a certain way -* Build a culture of craft and critical thinking in our teams -* Empower our teams to build better +* 减少在不理解的情况下就改动代码所引发的事故的数量。 +* 减少花费在理解软件为什么这样编写上的时间。 +* 在我们的团队中建立一种包含工匠精神和批判性思考的文化。 +* 赋能我们的团队以更好地编程。 -#### When to write “why” +#### 什么时候记录“为什么”? -As you code, throughout the day, ask yourself: +在你编写代码的时候,一直问自己: -> Are there certain constraints that are impacting my work? +> 是否有某些影响我工作的约束? > -> Is there anything I’m doing that requires explanation to understand fully? +> 我在做的事情中有没有需要解释才能完全明白的? -These constraints can be related to: +这些约束可能和这些事情有关: -* Tight deadlines -* Lacking resources on a project -* Known bugs we’d like to mitigate -* User-traffic patterns +* 时间紧迫。 +* 项目资源不足。 +* 我们想要缓解的已知缺陷。 +* 用户流量模式。 -Some building that requires explanation can be: +一些需要解释的编码方式可能是这样的: -* Why we decided to duplicate code instead of reuse code -* Why we’re committing code in this order -* Why we have this unusual-looking edge case handling +* 我们为什么决定复制代码而不是复用代码? +* 我们为什么以这种顺序提交代码? +* 我们为什么要处理这种特别的极端情况? -Write those constraints and explanations down. +把这些限制和解释一一记录下来。 -#### Example constraints +#### 约束的案例 -* This feature needs to be released immediately, so we’re accepting poorer code quality -* We need to support backward compatibility of our iOS app to v1.1, so we have to pass this extra field -* We’re expecting a 100x burst load tomorrow, so we’re increasing our base instance size -* Team size is three engineers, so we only want to support one programming stack -* Our project requirements aren’t clear enough, so we’re holding off on updating this feature for now +* 这个功能需要立即发布,所以我们接受较差的代码质量。 +* 我们需要向后兼容我们的 iOS 应用到 1.1 版本,所以我们不得不传递这个额外的参数。 +* 我们预计明天的负载会暴增 100 倍,所以我们增加了基础实例大小。 +* 我们的团队只有三个工程师,所以我们只能支持一种技术栈。 +* 我们的项目的需求不够清晰,所以我们暂时不更新这个功能。 -#### Example explanations +#### 解释的案例 -* We’re duplicating our blog model because we want to move to a backward-incompatible model -* We’re avoiding our usual API for this because that API has been known to cause performance issues in use cases like ours -* The special account balance value of -$200 indicates this is an employee account +* 我们复制了博客模型,因为我们想要迁移到一个向后不兼容的模型。 +* 我们在这里避免使用常用的 API,因为在我们这样的用例中,这个 API 会导致性能问题。 +* -$200 的特殊账户余额意味着这是一个员工账户。 -## How to Make “Why” Easy to Find +## 如何更容易地发现“为什么” -Part 2 of this post will cover how you can use Git to capture the “why” related to your code as part of your daily routine. +这篇文章的第二部分会讲述在日常工作中如何使用 Git 来捕捉和你的代码有关的“为什么”。 -We’ll also go over how Bytebase makes sharing and finding the “why” behind your team’s work easy. +我们也会复盘 Bytebase 是如何让发现和分享团队工作中隐藏的“为什么”变得容易的。 -## References +## 参考资料 -[**Write Good Documentation**](https://hackernoon.com/write-good-documentation-6caffb9082b4) +* [**撰写优秀的文档**](https://hackernoon.com/write-good-documentation-6caffb9082b4) -* [“Etsy’s experiment with immutable documentation”](https://codeascraft.com/2018/10/10/etsys-experiment-with-immutable-documentation/) via Code as Craft -* [“What I Learned from the Story of the Fish”](https://ptexgroup.com/learned-story-fish/) via Ptex Group +* [“Etsy 的关于不可变更的文档的实验”](https://codeascraft.com/2018/10/10/etsys-experiment-with-immutable-documentation/) 来自 Code as Craft +* [“我从鱼的故事获得的感悟”](https://ptexgroup.com/learned-story-fish/)来自 Ptex Group > 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 diff --git a/TODO1/vuejs-3-0-0-beta-features-im-excited-about.md b/TODO1/vuejs-3-0-0-beta-features-im-excited-about.md new file mode 100644 index 00000000000..bb9ff378919 --- /dev/null +++ b/TODO1/vuejs-3-0-0-beta-features-im-excited-about.md @@ -0,0 +1,105 @@ +> * 原文地址:[VueJS 3.0.0 Beta: Features I’m Excited About](https://blog.bitsrc.io/vuejs-3-0-0-beta-features-im-excited-about-c70b82fac163) +> * 原文作者:[Nwose Lotanna](https://medium.com/@viclotana) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/vuejs-3-0-0-beta-features-im-excited-about.md](https://github.com/xitu/gold-miner/blob/master/TODO1/vuejs-3-0-0-beta-features-im-excited-about.md) +> * 译者: +> * 校对者: + +# VueJS 3.0.0 Beta: Features I’m Excited About + +![](https://cdn-images-1.medium.com/max/2560/1*RldyrFWyMYS5mhvUNmkw7g.jpeg) + +As of this writing VueJS 3.0.0 is now in Beta, in this article we will look at a quick overview of the Journey to the big release as presented by the Vue team at the latest ThisDot meetup online. + +![](https://cdn-images-1.medium.com/max/2952/1*jfs5yQ21kQKLCvbvuHmSXA.png) + +## Vue JS + +Vue JS is a very popular and progressive JavaScript library created by Evan You and 284+ members of the Vue community. It has more than 1.2 million users and consists of an approachable core library that focuses on the view layer only, and an ecosystem of supporting libraries that helps you tackle complexity in large Single-Page Applications. + +#### The Eco-System + +It’s quite amazing to see how vast Vue’s eco-system has become. One of the things I’m particularly excited about is the recent release of [**Bit.dev**](https://bit.dev) with support for VueJS. So, now, finally, Vue developers can publish, document, and organize reusable components in a cloud component hub (just like React developers). Every new VueJS library or tool that comes out strengthens this great framework but some are more impactful than others (not having the freedom to publish components from any codebase is a deal-breaker for many developers). + +![Published React components on [Bit.dev](https://bit.dev) — now supports VueJS](https://cdn-images-1.medium.com/max/2000/1*Nj2EzGOskF51B5AKuR-szw.gif) + +#### This Dot Meetup + +During this pandemic period, the ThisDot meetup was held on the 16th of April online where the core team showed what is to come in the future with Vue JS and that is what we will summarize in this post. + +## Performance + +This new version of Vue JS is built for speed, there is a significant speed bump between version 3.0 and the previous versions of Vue. It has up to 2x better update performance and up to 3x faster for server-side renderings. The component initialization is also now more efficient, with even compiler-informed fast paths to execution. The virtual DOM was also totally re-written and this new version will be totally faster than ever. + +## Tree-shaking support + +Support is now also available in this version for things like tree-shaking. Most features that were optional in Vue are now tree-shakable, features like transition and v-model. This has drastically reduced the size of Vue applications, a bare-bone HelloWorld is now 13.5kb in file size and with the composition API support it can go as low as 11.75kb in file size. With all the runtime features included, a project can weigh as small as 22.5kb. This means that even with the addition of way more features, Vue 3.0 is still lighter than any 2.x version. + +## Composition API + +The Vue team has introduced a new way to deal with code organization, initially in the 2.x versions we used options. Options are great but it has compiler drawbacks when trying to match or access Vue logic, also having to deal with JavaScript’s this too. So the composition API is a better solution for handling these and it also comes with freedom and flexibility to use and re-use pure JS functions in your Vue components which would result to use less lines of code entirely. The composition API looks like this: + +```js + +``` + +Do we now lose the options API? No, rather the composition API would be used side by side with the options API. (This reminds me so much of React hooks) + +## Fragments + +Just like React, Vue JS will introduce fragments in Vue version 3.0.0, one of the main needs for fragments is that Vue templates can only have one tag. So a code block like this in a Vue template will return an error: + +```html + +``` + +The first place I saw this idea implemented was in React 16, fragments are template wrapper tags that are used to structure your HTML code but does not alter the semantics. Like a Div tag but this time without any effect on the DOM. With fragments, manual render functions can just return arrays and it just works like you it does in React. + +## Teleport + +Teleports which were previously called portals are safe channels for rendering child nodes into DOM nodes outside the DOM lineage like for pop-ups and even modals. Before now, this is usually handled with a lot of pain in CSS, now Vue lets you use \ to handle that in your template section. I believe teleport was inspired by React portals and it will be shipped with the version 3.0.0 of Vue JS. + +## Suspense + +Suspense is a component required during lazy loading basically used to wrap lazy components. Multiple lazy components can be wrapped with the suspense component. In the 3.0.0 version of Vue JS suspense will be introduced to wait on nested async dependencies in a nested tree and it will work well with async components. + +## Better TypeScript support + +Vue started to support TypeScript from versions in the 2.x and for version 3.0.0 is continuing to do so. So generating new projects with the current latest TypeScript version will be possible with Vue 3.0.0 with TSX support and no much difference between the TS and the JS code and the APIs. Class component is still supported ([vue-class-component@next](https://github.com/vuejs/vue-class-component/tree/next) is currently in alpha) + +## Version 3.0.0 Status Report + +Initial official release plans for the version 3.0.0 of Vue JS was slated for [first quarter of 2020](https://github.com/vuejs/vue/projects/6) according to the timeline on the project on GitHub. Starting from 16th of April 2020, the Vue version 3.0.0 is now in beta! This means that all planned request for comments have been worked on and implemented and the team’s focus is now on library integrations. There is now available an experimental support for [the Vue CLI here](https://github.com/vuejs/vue-cli-plugin-vue-next) and there is a very simple single file component support based on [Webpack here](https://github.com/vuejs/vue-next-webpack-preview). + +## One more release + +Vue version 2.7 which is a minor release will be out soon and it will probably be the last version in the 2.x series before the official release of the 3.0.0 version. It is going to back port compatible improvements from the version 3.0.0 and show depreciation warnings for features that would not be in 3.0.0. + +## Want to support… + +Chances are low but you might run into inconsistencies with the 2.x versions and you have to check if that issue’s fix has already been proposed in a RFC and if it is not, open an issue. Remember to [read the issue helper](https://new-issue.vuejs.org/?repo=vuejs/vue-next) to guide you through opening new issues. + +## Conclusion + +This is an overview of the features shipping with the third version of Vue JS. The team at Vue has made sure that this version is the fastest frontend framework in the market. You can view the slides to the ThisDot online meetup [here](https://t.co/7TP5ZMtjK4?amp=1), stay safe and happy hacking. What is your favorite new feature? + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[区块链](https://github.com/xitu/gold-miner#区块链)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/web-scraping-with-puppeteer-in-node-js.md b/TODO1/web-scraping-with-puppeteer-in-node-js.md new file mode 100644 index 00000000000..6ce9c89441a --- /dev/null +++ b/TODO1/web-scraping-with-puppeteer-in-node-js.md @@ -0,0 +1,137 @@ +> * 原文地址:[Web Scraping with Puppeteer in Node.js](https://medium.com/javascript-in-plain-english/web-scraping-with-puppeteer-in-node-js-4a32d85df183) +> * 原文作者:[Belle Poopongpanit](https://medium.com/@bellex0) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/web-scraping-with-puppeteer-in-node-js.md](https://github.com/xitu/gold-miner/blob/master/TODO1/web-scraping-with-puppeteer-in-node-js.md) +> * 译者: +> * 校对者: + +# Web Scraping with Puppeteer in Node.js + +![](https://cdn-images-1.medium.com/max/2328/1*laoZh8fB6iCGTuBbR_2zig.png) + +Have you ever wanted to use your favorite company’s or favorite website’s API for a new app project and discovered that they either a) never had one to begin with or b) eradicated it’s API for public use? (I’m looking at you, Netflix) Well, that’s what happened to me and being the persistent person that I am, I uncovered a solution for this problem: web scraping. + +Web scraping is the technique of automatically extracting and collecting data from a website using software. After collecting the data, you can use it to make your own API’s. + +There are numerous technologies that can be used for web scraping; Python is a popular language that is used. However, I am a JavaScript kinda girl. So, in this blog I will use Node.js and Puppeteer. + +Puppeteer is a Node.js library that allows us to run a Chrome browser in the background (called a headless browser since it doesn’t need a graphic user interface) and helps extract data from a website. + +Since the majority of us have been held hostage in our homes by COVID-19, Netflix binge-watching has been a popular pastime for many (what else are we supposed to do aside from crying?). To support my fellow indecisive and bored Netflix-watchers, I found a website, [https://www.digitaltrends.com/movies/best-movies-on-netflix/](https://www.digitaltrends.com/movies/best-movies-on-netflix/) that lists the current best movies to watch in April 2020. Once I scrape this page and retrieve the data, I want to store it in a JSON file. That way, if I ever need a best current Netflix movies API, I can go back and obtain the data from this repository. + +## Getting Started + +To get started, I created a folder in my VSCode called `webscraper` . Within the folder, I will create a file called `netflixscrape.js` . + +In the terminal, we need to install puppeteer. + +```bash +npm i puppeteer +``` + +Next, we need to import the required modules and libraries. The first lines of code in `netflixscrape.js` will be: + +```js +const puppeteer= require('puppeteer') +const fs = require('fs') +``` + +`fs` is the Node.js file system module. We will need to use this to create a JSON file from our data. + +## Writing the scrape function + +Let’s begin coding our `scrape()` function. + +```js +async function scrape (url) { + const browser = await puppeteer.launch(); + const page = await browser.newPage(); + await page.goto(url) +``` + +`scrape()` is going to take in an argument of a url. We start up our browser using `puppeteer.launch()` . `browser.newPage()` opens a blank page on the browser. Then, we tell the browser to go to the specified url. + +#### How to retrieve the data we want to scrape + +In order to scrape the data we want from the site, we need to grab the data using their specific HTML elements. Go to [https://www.digitaltrends.com/movies/best-movies-on-netflix/](https://www.digitaltrends.com/movies/best-movies-on-netflix/) and open up Inspector/Chrome DevTools. To do this: + +“command + option + j” on a Mac or “control + shift + j” on Windows + +Since I want to grab the movies’ titles and their summaries from the article, I see that I have to select `h2` elements (title) and its’ sibling `p` elements (summary). + +![h2 for movie title](https://cdn-images-1.medium.com/max/5724/1*BEQd106SvxT1_jGuS4I23A.png) + +![p for movie summary](https://cdn-images-1.medium.com/max/5760/1*RH8gGDJeIGE8Wz3VcDgyaQ.png) + +#### How to manipulate the retrieved data + +Continuing with our code: + +```js +var movies = await page.evaluate(() => { + var titlesList = document.querySelectorAll('h2'); + var movieArr = []; + + for (var i = 0; i < titlesList.length; i++) { + movieArr[i] = { + title: titlesList[i].innerText.trim(), + summary: titlesList[i].nextElementSibling.innerText.trim() + }; +} +return movieArr; +}) +``` + +`page.evaluate()` is used to enter the DOM of the website and allows us to run custom JavaScript code as if we were executing it in the DevTools console. + +`document.querySelector('h2')` selects ALL `h2` elements on the page. We save them all in the `titlesList` variable. + +Then, we create an empty array called `movieArr`. + +We want to save each movie’s title and summary in their own individual object. In order to do this, we run a **for loop**. The loop indicates that each element in the `movieArr` is equal to an object with properties of `title` and `summary`. + +To get the movie title, we have to go through the `titlesList`, which are all `h2` element nodes. We apply the `innerText` property to get the text of the `h2`. Then, we apply the `.trim()` method to remove any white space. + +If you’ve carefully navigated through the DevTools console, you’ll notice that the page has many `p` elements with no unique classes or id’s. Thus, it’s really difficult to precisely grab the summary `p` element we need. In order to get around this, we call the `.nextElementSibling` property on the `h2` node (titlesList[i]). When you take a closer look at the console, you see that the `p` element of the summary is a sibling of the `h2` element of the title. + +#### Storing the scraped data to a JSON file + +Now that we’ve completed the main data extraction part, let’s store all of this in a JSON file. + +```js +fs.writeFile("./netflixscrape.json", JSON.stringify(movies, null, 3), (err) => { + if (err) { + console.error(err); + return; + }; + console.log("Great Success"); +}); +``` + +`fs.writeFile()` creates a new JSON file containing our movies data. It takes in 3 arguments: 1) the name of the file to be created + +2) **JSON**. **stringify**() method converts a JavaScript object to a **JSON** string. Takes in 3 arguments here. The object: `movies`, replacer (which filters out what properties you do or don’t want to be included): `null `, and space (used to insert white space into the output JSON string for readability): `3` . This way essentially makes the JSON file prettier and clean. + +3) `err`, in case we get an error + +`err` takes a callback function which states that if there is an error, console.log the error. If there is no error, console.log “Great Success.” + +Finally, putting the whole code together: + +We add `browser.close()` to close the puppeteer browser. We call the `scrape()` function in the last line with our url. + +## Last Step: Run scrape() function + +Let’s run this code by typing `node netflixscrape.js` in the terminal. + +If all goes well (which it should), you will get “Great Success” in your console and you will see a newly created JSON file with all the Netflix movie titles and summaries. + +![](https://cdn-images-1.medium.com/max/5220/1*J8LazvNXbPlTgSCTs0n5cQ.png) + +Congrats!!👏 You’re officially a hacker! Just kidding. But now you know how to scrape the web to obtain data and create your own API’s, which is so much more thrilling. + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[区块链](https://github.com/xitu/gold-miner#区块链)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/what-is-accessibility-and-why-is-it-crucial-for-your-users-experience.md b/TODO1/what-is-accessibility-and-why-is-it-crucial-for-your-users-experience.md new file mode 100644 index 00000000000..83ec028213c --- /dev/null +++ b/TODO1/what-is-accessibility-and-why-is-it-crucial-for-your-users-experience.md @@ -0,0 +1,45 @@ +> * 原文地址:[What is accessibility and why is it crucial for your user’s experience?](https://medium.com/ux-in-plain-english/what-is-accessibility-and-why-is-it-crucial-for-your-users-experience-2079e9829076) +> * 原文作者:[Ania Puchala](https://medium.com/@itsaniahere) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/what-is-accessibility-and-why-is-it-crucial-for-your-users-experience.md](https://github.com/xitu/gold-miner/blob/master/TODO1/what-is-accessibility-and-why-is-it-crucial-for-your-users-experience.md) +> * 译者: +> * 校对者: + +# What is accessibility and why is it crucial for your user’s experience? + +![Illustration made using Humaaans by Pablo Stanley](https://cdn-images-1.medium.com/max/3000/1*qYEhqdw3LaYgol-wrjrjDg.png) + +We are all different. We may have different gender, age, culture, health conditions. But probably we use many common products. So what makes them usable for so many different people? Accessibility. + +Let’s see what accessibility stands for. + +Following Wikipedia, “accessibility refers to the design of products, devices, services, or environments for people who experience disabilities.” + +What kind of pictures appeared in your mind when you read the word “disabilities”? Was it a man in a wheelchair? Or maybe a deaf woman? Or a blind child? + +These are probably the most common understandings of disabilities. But are they showing the spectrum of the problem? + +In fact, disability literally means that the person is not able to do something using their senses, body or mind for some reason. And there are plenty of possible reasons. + +World Health Organization says that disability is context depended. It means that “Disability is not just a health problem. It is a complex phenomenon, reflecting the interaction between features of a person’s body and features of the society in which he or she lives.” In other words, each situation that you are in defines your ability to achieve your goal. When the person has a broken arm she will not be able to use their smartphone using both hands. But the same experience would get the person on a crowded bus while holding a handrail. And if someone is in the loud restaurant he will not be able to hear the video — he would need, for example, the closed caption. + +In Inclusive Design Toolkit, Microsoft called it the persona spectrum. It reveals the range of users’ disabilities depending on the context. Following the toolkit — users can have either permanent, temporary or situational disabilities. This knowledge can help you analyze your product in terms of not obvious use cases. If your product meets a deaf person’s needs, the person with an ear infection or who stays in a loud restaurant will benefit from it as well. + +When you solve the accessibility problem for one group, you actually extend the value of it to many other people who will use your solution in temporary or situational circumstances. Thanks to that you can include as many people as possible in a similar experience of similar quality and value. + +![](https://cdn-images-1.medium.com/max/2000/1*c54uroDrwHzhtVj6CE-3cg.png) + +If you want to comprehensively evaluate your product in terms of accessibility, get familiar with WCAG standards. This guideline created by W3C organization provides complex recommendations for creating web and mobile products, so as many people as possible can use it in any circumstances. And remember, when you cover the accessibility problems for the group of permanently disabled people, a lot more people will benefit from it. + +--- + +Read more: + +- [https://www.microsoft.com/design/inclusive/](https://www.microsoft.com/design/inclusive/) +- [https://www.w3.org/WAI/standards-guidelines/wcag/](https://www.w3.org/WAI/standards-guidelines/wcag/) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 + +--- + +> [掘金翻译计划](https://github.com/xitu/gold-miner) 是一个翻译优质互联网技术文章的社区,文章来源为 [掘金](https://juejin.im) 上的英文分享文章。内容覆盖 [Android](https://github.com/xitu/gold-miner#android)、[iOS](https://github.com/xitu/gold-miner#ios)、[前端](https://github.com/xitu/gold-miner#前端)、[后端](https://github.com/xitu/gold-miner#后端)、[区块链](https://github.com/xitu/gold-miner#区块链)、[产品](https://github.com/xitu/gold-miner#产品)、[设计](https://github.com/xitu/gold-miner#设计)、[人工智能](https://github.com/xitu/gold-miner#人工智能)等领域,想要查看更多优质译文请持续关注 [掘金翻译计划](https://github.com/xitu/gold-miner)、[官方微博](http://weibo.com/juejinfanyi)、[知乎专栏](https://zhuanlan.zhihu.com/juejinfanyi)。 diff --git a/TODO1/what-is-google-tag-manager-and-why-use-it.md b/TODO1/what-is-google-tag-manager-and-why-use-it.md index 29a8be6149a..648ab895dfe 100644 --- a/TODO1/what-is-google-tag-manager-and-why-use-it.md +++ b/TODO1/what-is-google-tag-manager-and-why-use-it.md @@ -2,171 +2,171 @@ > * 原文作者:[Amanda Gant](https://www.orbitmedia.com/team/amanda-gant/) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/what-is-google-tag-manager-and-why-use-it.md](https://github.com/xitu/gold-miner/blob/master/TODO1/what-is-google-tag-manager-and-why-use-it.md) -> * 译者: -> * 校对者: +> * 译者:[zhanght9527](https://github.com/zhanght9527) +> * 校对者:[xionglong58](https://github.com/xionglong58) -# What is Google Tag Manager and why use it? The truth about Google Tag Manager +# Google Tag Manager 的真相大揭秘 -If you're not that familiar with Google Tag Manager, you are probably wondering what it is and why you should use it. Let's answer the most common questions around Google Tag Manager. +如果你对 Google Tag Manager 不太熟悉,你可能会想知道它是什么以及为什么要使用它。让我们来回答关于 Google Tag Manager 的一些最常见的问题。 -- What is Google Tag Manager? -- How is it different from Google Analytics? -- Is it easy to use? -- Why should I use Google Tag Manager? - - What are the benefits? - - What are the downfalls? -- What can I track in Google Tag Manager? -- Where can I learn more about Google Tag Manager? +- 什么是 Google Tag Manager ? +- 它和 Google Analytics 有哪些不同? +- 它用起来容易吗? +- 为什么我们要使用 Google Tag Manager ? + - 它有哪些好处? + - 它还存在哪些问题? +- 我们可以在 Google Tag Manager 中跟踪哪些东西? +- 在哪里可以了解更多关于 Google Tag Manager 的信息? -## What is Google Tag Manager (GTM)? +## 什么是 Google Tag Manager(GTM)? -Google Tag Manager is a free tool that allows you manage and deploy marketing tags (snippets of code or tracking pixels) on your website (or mobile app) without having to modify the code. +Google Tag Manager 一款允许你在你的网站(或移动应用)上管理和部署营销跟踪代码(代码片段或跟踪像素)而不必修改任何代码的工具,并且它是完全免费的。 -Here's a very simple example of how GTM works. Information from one data source (your website) is shared with another data source (Analytics) through Google Tag Manager. GTM becomes very handy when you have lots of tags to manage because all of the code is stored in one place. +下面是一个 GTM 如何工作的非常简单的例子。来自一个数据源(你的网站)的信息通过 Google Tag Manager 与另一个数据源(Analytics)共享。当有很多代码需要管理时,GTM 变得非常方便,因为所有的代码都存储在一个地方。 ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/GTM-v2.jpg) -A huge benefit of Tag Manager is that you, the marketer, can manage the code on your own. "No more developers needed. Whoo hoo!" +Tag Manager 的一个巨大的好处就是,作为运营人员,你可以自己管理代码。“终于不再需要开发小哥哥了。Whoo hoo!” -Sounds easy right? Unfortunately, it's not that simple. +听起来很简单吧?不幸的是,事情没那么简单。 -## Is Google Tag Manager easy to use? +## Google Tag Manager 易于上手吗? -According to Google, +根据 Google 的说法, -> "Google Tag Manager helps make tag management ***simple***, ***easy*** and ***reliable*** by allowing marketers and webmasters to deploy website tags all in one place." +> “Google Tag Manager 允许运营人员和网站管理员在同一个位置部署网站跟踪代码,从而帮助实现代码管理的**简单性**、**易用性**和**可靠性**。 -They say it's a "simple" tool that any marketer can use without needing a web developer. +他们说,这是一个“简单”的工具,任何运营人员都可以使用,而不需要网络开发人员。 -I may get run over in the comments section for saying this, but I'm standing my ground. **Google Tag Manager is not "easy" to use without some technical knowledge or training (courses or self-taught).** +我可能会因为说下面的话而在评论区里被人批评,但我仍然坚持自己的立场。**如果没有一些技术知识或培训(课程或自学),Google Tag Manager 使用起来还是比较困难的。** -You have to have *some* technical knowledge to understand how to set up tags, triggers and variables. If you're dropping in Facebook pixels, you'll need *some* understanding of how Facebook tracking pixels work. +你必须具备**一些**技术知识才能理解如何设置跟踪代码、触发器和变量。如果你也在使用 Facebook pixels,你也需要了解**一些** Facebook 像素追踪的原理。 -If you want to set up event tracking in Google Tag Manager, you'll need *some* knowledge about what "events" are, how Google Analytics works, what data you can track with events, what the reports look like in Google Analytics and how to name your categories, actions and labels. +如果你想在 Google Tag Manager 中设置事件跟踪,你需要了解**一些**关于什么是“事件”、Google Analytics 的工作原理、你可以用事件跟踪什么数据、Google Analytics 中的报告是什么样子以及如何命名你的类别、动作和跟踪代码的知识。 -Although it is "easy" to manage multiple tags in GTM, there is a learning curve. Once you're over the hump, GTM is pretty slick about what you can track. +虽然在 GTM 中管理多个跟踪代码是很“容易的”,但是也有一个学习曲线。一旦你越过了这个障碍,你便能轻松地驾驭 GTM。 -## Let's go over how Google Tag Manager works... +## 让我们看看 Google Tag Manager 是如何工作的... -There are three main parts to Google Tag Manager: +Google Tag Manager 有三个主要部分: -- **Tags**: Snippets of Javascript or tracking pixels -- **Triggers**: This tells GTM when or how to fire a tag -- **Variables**: Additional information GTM may need for the tag and trigger to work +- **跟踪代码**:Javascript 片段或跟踪像素 +- **触发器**:告诉 GTM 何时或如何触发跟踪代码 +- **变量**:GTM 可能需要的能使代码和触发器正常工作的附加信息 -### What are tags? +### 什么是跟踪代码? -Tags are snippets of code or tracking pixels from third-party tools. These tags tell Google Tag Manager ***what*** to do. +跟踪代码来自第三方工具的代码片段或跟踪像素。这些代码告诉 Google Tag Manager 需要去做**什么**。 -Examples of common tags within Google Tag Manager are: +Google Tag Manager 中常见的跟踪代码示例如下: -- Google Analytics Universal tracking code -- Adwords Remarketing code -- Adwords Conversion Tracking code -- Heatmap tracking code (Hotjar, CrazyEgg, etc...) +- Google Analytics Universal tracking 代码 +- Adwords Remarketing 代码 +- Adwords Conversion Tracking 代码 +- Heatmap tracking 代码(Hotjar、CrazyEgg 等) - Facebook pixels ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/examples-of-tags.png) -### What are triggers? +### 什么是触发器? -Triggers are a way to fire the tag that you set up. They tell Tag Manager ***when*** to do what you want it to do. Want to fire tags on a page view, link click or is it custom? +触发器是触发设置的跟踪代码的方法。它们告诉 Tag Manager **什么时候**去做你想做的事情。是要在页面视图中触发代码,单击链接还是自定义? ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/example-triggers.png) -### What are variables? +### 什么是变量? -Variables are additional information that GTM ***may*** need for your tag and trigger to work. Here are some examples of different variables. +变量是 GTM 跟踪代码和触发器工作**可能**需要的附加信息。下面是一些不同变量的例子。 ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/example-variables-2.png) -The most basic type of constant variable that you can create in GTM is the Google Analytics UA number (the tracking ID number). +在 GTM 中可以创建的最基本的常量变量类型是 Google Analytics UA number(跟踪 ID 编号)。 ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/example-variables.png) -Those are the very basic elements of GTM that you will need to know to start managing tags on your own. +这些是 GTM 的一些基本元素,你需要知道这些元素才能开始自己管理跟踪代码。 -If you're bored reading this right now, you won't have any issues managing your tags. If you are completely lost, you are going to need help from someone more technical. +如果读到这里你已经觉得枯燥的话,那么证明你可以熟练管理跟踪代码了。如果你仍然没有头绪,那么你需要去寻求技术人员的帮助了。 -## How is Google Tag Manager different from Google Analytics? +## Google Tag Manager 和 Google Analytics 有哪些不同? -Google Tag Manager is a completely different tool used only for storing and managing third-party code. There are no reports or any way to do analysis in GTM. +Google Tag Manager 是一个只用于存储和管理第三方代码的完全不同的工具,在 GTM 中没有任何报告或分析的功能。 ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/gtm-workspace.png) -Google Analytics is used for actual reporting and analysis. All conversion tracking goals or filters are managed through Analytics. +Google Analytics 用于实际的报告和分析。所有转化跟踪目标(Goals)和过滤器(Filters)都通过 Analytics 进行管理。 ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/filters-goals.png) -All reporting (conversion reports, custom segments, ecommerce sales, time on page, bounce rate, engagement reports, etc...) are done in Google Analytics. +所有的报告(目标转化报告、自定义细分报告、电子商务销售报告、用户页面停留时间、用户跳出率、用户参与度报告等)都在 Google Analytics 中完成。 ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/google-analytics.png) -## What are the benefits of Google Tag Manager? +## Google Tag Manager 有哪些好处? -Once you get over the learning curve, what you can do in Google Tag Manager is pretty amazing. You can customize the data that is sent to Analytics. +一旦你越过了学习曲线,你可以在 Google Tag Manager 中做很多神奇的事情。你可以自定义发送到 Analytics 的数据。 -You can setup and track basic events like PDF downloads, outbound links or button clicks. Or, complex enhanced ecommerce product and promotion tracking. +你可以设置和跟踪基本事件,如 PDF 下载、出站链接或按钮单击。或者,复杂的增强电子商务产品和促销跟踪也可以设置。 -Let's say we want to track all outbound links on the website. In GTM, choose the category name, action and label. We chose offsite link, click and click URL. +假设我们想要跟踪网站上所有的出站链接。在 GTM 中,选择类别名称、动作和代码。我们选择站外链接,点击和点击 URL 。 ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/customize-data.png) -In Google Analytics go to Behavior > Events > Top Events > Offsite link. +在 Google Analytics 前往 Behavior > Events > Top Events > Offsite link。 ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/ga-events.png) -Now select either event action or label to get the full reports. The data that we setup in Google Tag Manager is now appearing in the Analytics reports. Nifty! +现在,选择事件动作或代码以获得完整的报告。我们在 Google Tag Manager 中设置的数据现在出现在分析报告中。漂亮! ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/event-action-label.png) -Want to try out a tool on a free trial basis? You can add the code to Tag Manager and test it out without needing to get your developers involved. +想要自由试用某个工具吗?你可以将代码添加到 Tag Manager 并进行测试,而不需要让开发人员参与其中。 ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/freetrial.png) -Other perks: +小贴士: -- It *may* help your site load faster depending on how many tags you are using. -- It works with non-Google products. -- Flexibility to play around and test out almost anything you want. -- All third-party code is in one place. -- GTM has a preview and debug mode so you can see what's working and what's not before you make anything live. It shows you what tags are firing on the page. *Love this feature!* +- 它**可能**帮助加快你的网站加载速度但取决于你使用了多少跟踪代码。 +- 它适用于非 Google 的产品。 +- 你可以灵活玩转并测试几乎任何你想要的东西。 +- 所有的第三方代码都在一个地方。 +- GTM 有一个预览和调试模式,所以在你做任何事情之前,你可以看到哪些是有效的,哪些不是。它会向你展示页面上正在触发的跟踪代码。**爱死这个功能了!** ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/preview-mode.png) -## What are the drawbacks? +## 缺点是什么? -**1\. You must have some technical knowledge**, even for the basic setup. +**1\. 你必须得有一些技术知识**,即使是基本设置。 -Check out the [documentation from Google](https://developers.google.com/tag-manager/devguide#thepits) on how to setup Google Tag Manager. Once you get past the "Quick Start Guide," it takes you to a developer guide. Not a marketer's guide. If you are a first time user, this will read like gibberish. +查看 [documentation from Google](https://developers.google.com/tag-manager/devguide#thepits) 如何设置 Google Tag Manager。一旦你读完了“快速入门指南”,它会让你感觉到这是一个开发指南,而不是运营指南。如果你是第一次使用,这会读起来像胡言乱语。 ![](https://www.orbitmedia.com/wp-content/uploads/2017/03/developer-guide.png) -**2\. It's a time investment.** +**2\. 这是一个时间投资。** -Unless you're a seasoned developer, you will need to carve out a chunk of research and testing time. Even if it's reading a few blog posts or taking an online class. +除非你是一名经验丰富的开发人员,否则你需要留出大量的研究和测试时间。即使是在看一些博客文章或者上一堂网络课。 -**3\. Make time for troubleshooting issues.** +**3\. 腾出时间解决问题。** -There is a lot of troubleshooting that takes place when setting up tags, triggers and variables. Especially if you are not in Tag Manager regularly, it's very easy to forget what you just learned. For more complex tags, you will likely need a developer with knowledge of how the site was built. +在设置标记、触发器和变量时,会产生很多问题。尤其是如果你不是经常在 Tag Manager 中工作,会很容易让你忘记刚刚学到的东西。对于更复杂的标记,你可能需要一个了解网站构建方式的开发人员。 -## What can you track in GTM? +## 你可以在 GTM 跟踪什么? -- Events (link clicks, PDF downloads, add to cart click, remove from cart click) -- Scroll tracking -- Form abandonment -- Shopping cart abandonment -- [Video views tracking](https://www.orbitmedia.com/blog/tracking-video-views-google-analytics-tag-manager/) -- [All exit link clicks](https://www.orbitmedia.com/blog/whered-they-go-track-every-exit-click-using-google-tag-manager-in-10-steps/) -- ...?????? +- 事件(链接点击、PDF 下载、添加购物车点击、删除购物车点击) +- 滚动跟踪 +- 表单被放弃 +- 购物车被遗弃 +- [视频播放次数跟踪](https://www.orbitmedia.com/blog/tracking-video-views-google-analytics-tag-manager/) +- [所有退出链接的单击](https://www.orbitmedia.com/blog/whered-they-go-track-every-exit-click-using-google-tag-manager-in-10-steps/) +- ...... -We are just scratching the surface of what you can do in Google Tag Manager. The possibilities seem almost endless. But, as[ Himanshu Sharma points out](https://www.optimizesmart.com/may-no-longer-need-google-tag-manager/), the more tags and data sources you have the harder they are to manage. +我们只是粗略地介绍了一下你在 Google Tag Manager 中能做些什么,但是你可以做的东西似乎无穷无尽。但是,正如 [Himanshu Sharma 指出](https://www.optimizesmart.com/may-no-longer-need-google-tag-manager/) ,跟踪代码和数据源越多,管理起来就越困难。 -## Where can I learn more about Google Tag Manager? +## 我在哪里可以了解更多关于 Google Tag Manager 的信息? -I took a live course through Conversion XL with Chris Mercer. It was one of the best online classes I've taken. You can [purchase the recordings](https://conversionxl.com/institute/live-courses/#view-recordings) if you are interested. +我和 Chris Mercer 通过 Conversion XL 参加了一个现场课程,这是我上过的最好的在线课程之一。如果你感兴趣,可以 [购买视频](https://conversionxl.com/institute/live-courses/#view-recordings) 。 -Other go-to resources are: +其他可以参考的资源: - Himanshu Sharma, [Optimize Smart blog](https://www.optimizesmart.com/may-no-longer-need-google-tag-manager/) - [Simo Hava blog](https://www.simoahava.com/) diff --git a/TODO1/what-on-earth-is-the-shadow-dom-and-why-it-matters.md b/TODO1/what-on-earth-is-the-shadow-dom-and-why-it-matters.md index 8a2780ce8bf..63a675d6d24 100644 --- a/TODO1/what-on-earth-is-the-shadow-dom-and-why-it-matters.md +++ b/TODO1/what-on-earth-is-the-shadow-dom-and-why-it-matters.md @@ -2,38 +2,38 @@ > * 原文作者:[Aphinya Dechalert](https://medium.com/@PurpleGreenLemon) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/what-on-earth-is-the-shadow-dom-and-why-it-matters.md](https://github.com/xitu/gold-miner/blob/master/TODO1/what-on-earth-is-the-shadow-dom-and-why-it-matters.md) -> * 译者: -> * 校对者: +> * 译者:[Renzi](https://github.com/mengrenzi) +> * 校对者:[niayyy](https://github.com/niayyy-S), [Siva](https://github.com/IAMSHENSH) -# What on Earth is the Shadow DOM and Why Does it Matter? +# 影子 DOM(Shadow DOM)到底是什么?它为什么重要? ![Photo by [Tom Barrett](https://unsplash.com/@wistomsin?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/shadow?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)](https://cdn-images-1.medium.com/max/6538/1*wDb9Aw5YXEM_O8DqrsG0vQ.jpeg) -> Isn’t one DOM enough? +> 一个 DOM 还不够吗? -We’ve all heard of the DOM at some point. It’s a topic that is quickly brushed over, and there’s not enough discussion of it. The name **shadow DOM** sounds somewhat sinister — but trust me, it’s not. +我们都曾听说过 DOM,它是一个容易被忽略的话题,而且对此也没有充足的讨论,**Shadow DOM** 这个名字听起来有点邪恶,但是相信我,它不是。 -The concept of DOMs is one of the foundations of the web and interfaces, and it’s deeply intertwined with JavaScript. +DOM 的概念是 Web 和界面的基础之一,并且与 JavaScript 息息相关。 -Many know what a DOM is. For starters, it stands for Document Object Model. But what does that mean? Why is it important? And how is understanding how it works relevant to your next coding project? +许多人都知道 DOM 是什么。首先,它代表文档对象模型。但这意味着什么?为什么它很重要?了解它如何工作与你的下一个编码项目有什么关系? --- -Read on to find out. +继续往下看吧。 -## What Exactly Is a DOM? +## DOM 到底是什么? -There’s a misconception that HTML elements and DOM are one and the same. However, they are separate and different in terms of functionality and how they are created. +有一种误解认为 HTML 元素和 DOM 是一回事。但是,它们在功能和创建方式上是独立且不同的。 -HTML is a markup language. Its sole purpose is to dress up content for rendering. It uses tags to define elements and uses words that are human-readable. It is a standardized markup language with a set of predefined tags. +HTML 是一种标记语言。其唯一目的是修饰呈现的内容。它使用标记来定义元素,并使用人类可读的单词。它是一种标准化的标记语言,具有一组预定义的标记。 -Markup languages are different from typical programming syntaxes because they don’t do anything other than create demarcations for content. +标记语言与典型的编程语法不同,因为标记语言只能用来创建内容分界。 -The DOM, however, is a constructed tree of objects that are created by the browser or rendering interface. In essence, it acts sort of like an API for things to hook into the markup structure. +然而,DOM 是由浏览器或渲染接口创建的对象构造的树。从本质上说,它有点像一个 API,用于将内容挂接到标记结构中。 -So what does this tree look like? +那么这棵树是什么样的呢? -Let’s take a quick look at the HTML below: +让我们快速浏览以下 HTML: ```HTML @@ -48,69 +48,69 @@ Let’s take a quick look at the HTML below: ``` -This will result in the following DOM tree. +这将生成下面的 DOM 树。 ![](https://cdn-images-1.medium.com/max/2000/1*J8n54a_p1jI6bPPJIKufbg.jpeg) -Since all elements and any styling in an HTML document exist on the global scope, it also means that the DOM is one big globally scoped object. +由于 HTML 文档中的所有元素和样式都位于全局范围内,因此这也意味着 DOM 是一个大的全局作用域内的对象。 -`document` in JavaScript refers to the global space of a document. The `querySelector()` method lets you find and access a particular element type, regardless of how deeply nested it sits in the DOM tree, provided that you have the pathway to it correct. +`document` 在 JavaScript 中是指文档的全局空间。`querySelector()` 方法允许你查找和访问特定的元素类型,而不管它在 DOM 树中的嵌套有多深,只要你有正确的路径即可。 -For example: +比如: ```js document.querySelector(".heading"); ``` -This will select the first element in the document with `class="heading"`. +这将选择文档中带有 `class="heading"` 的第一个元素。 -Suppose you do something like the following: +假设你执行以下操作: ```js document.querySelector("p"); ``` -This will target the first `\

` element that exists in a document. +这将指向文档中存在的第一个 `\

` 元素。 -To be more specific, you can also do something like this: +具体来讲,你还可以执行以下操作: ```js document.querySelector("h1.heading"); ``` -This will target the first instance of `\

`. +这将指向第一个 `\

` 实例。 -Using `.innerHTML = ""` will give you the ability to modify whatever sits in between the tags. For example, you can do something like this: +使用 `.innerHTML = ""` 将使你能够修改标签之间的任何内容。例如,你可以这样做: ```js document.querySelector("h1").innerHTML = "Moooo!" ``` -This will change the content inside the first `\

` tags to `Moooo!`. +这会将第一个 `\

` 标签内的内容更改为 `Moooo!`。 --- -Now that we have the basics of DOMs sorted, let’s talk about when a DOM starts to exist within a DOM. +现在,我们已经梳理了 DOM 的基础知识,下面我们来讨论存在于 DOM 中的 DOM 的来历。 -## DOM Within DOMs (aka Shadow DOMs) +## 存在于 DOM 中的 DOM(又名 Shadow DOM) -There are times when a straight single-object DOM will suffice for all the requirements of your web app or web page. Sometimes, though, you need third-party scripts to display things without it messing with your pre-existing elements. +有时候,一个简单的单一对象 DOM 就可以满足 Web 应用程序或 Web 页面的所有需求。但是,有时你需要第三方脚本来显示内容,而不想它与你现有的元素混淆。 -This is where shadow DOMs come into play. +这就是 Shadow DOM 发挥作用的地方。 -Shadow DOMs are DOMs that sit in isolation, have their own set of scopes, and aren’t part of the original DOM. +Shadow DOM 是独立存在的 DOM,有自己的作用域集,不属于原始 DOM 的一部分。 -Shadow DOMs are essentially self-contained web components, making it possible to build modular interfaces without them clashing with one another. +Shadow DOM 本质上是独立的 Web 组件,使构建模块化的接口而不会相互冲突成为了可能。 -Browsers automatically attach shadow DOMs to some elements, such as `\` , `\