From c2bb31edec46a47404b387ff16441a37a26e2155 Mon Sep 17 00:00:00 2001 From: lsvih Date: Fri, 31 Jul 2020 10:15:29 +0800 Subject: [PATCH 01/10] Create from-scratch-to-the-first-10-customers-how-i-designed-and-launched-a-saas-product.md (#7250) * Create from-scratch-to-the-first-10-customers-how-i-designed-and-launched-a-saas-product.md * Update from-scratch-to-the-first-10-customers-how-i-designed-and-launched-a-saas-product.md --- ...-i-designed-and-launched-a-saas-product.md | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 article/2020/from-scratch-to-the-first-10-customers-how-i-designed-and-launched-a-saas-product.md diff --git a/article/2020/from-scratch-to-the-first-10-customers-how-i-designed-and-launched-a-saas-product.md b/article/2020/from-scratch-to-the-first-10-customers-how-i-designed-and-launched-a-saas-product.md new file mode 100644 index 00000000000..b5c5c8ae5fc --- /dev/null +++ b/article/2020/from-scratch-to-the-first-10-customers-how-i-designed-and-launched-a-saas-product.md @@ -0,0 +1,212 @@ +> * 原文地址:[From scratch to the first 10 customers: How I designed and launched a SaaS product](https://codeburst.io/from-scratch-to-the-first-10-customers-how-i-designed-and-launched-a-saas-product-9176a8996b89) +> * 原文作者:[Valerio Barbera](https://medium.com/@valeriobarbera) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/article/2020/from-scratch-to-the-first-10-customers-how-i-designed-and-launched-a-saas-product.md](https://github.com/xitu/gold-miner/blob/master/article/2020/from-scratch-to-the-first-10-customers-how-i-designed-and-launched-a-saas-product.md) +> * 译者: +> * 校对者: + +# From scratch to the first 10 customers: How I designed and launched a SaaS product + +![Photo by [PhotoMIX Company](https://www.pexels.com/@wdnet?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels) from [Pexels](https://www.pexels.com/photo/black-samsung-tablet-computer-106344/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)](https://cdn-images-1.medium.com/max/12032/1*xNt4aprSuOo2bdYg9F-6gw.jpeg) + +Creating a successful software as a service (SaaS) product is the dream for many entrepreneurial-minded programmers. In the process of launching my own SaaS I discovered that sharing and comparing experiences with other founders is an essential part of this journey, and without this, I probably would never have created it at all. + +In this article, I’ll share the mental and practical processes that led me to create a SaaS product from scratch, and how I gained my first paying customers. Whether you are thinking about creating a new product or you have already launched, this article can help you compare your own strategies and methods with the ones that worked for me, and possibly adapt them for yourself. + +![](https://cdn-images-1.medium.com/max/2822/1*chMcZ5-kuoA-FvcH5R3FNg.png) + +I personally dedicate up to five hours per week researching the experiences of other founders. I’m always looking for new ideas, ways to avoid mistakes, and evaluating new strategies that could help me to obtain concrete results (that is, improve the product and increase customers’ happiness). + +For this reason, I decided to work in a completely frank and transparent way and share everything about my path — including what has been working and what has not — with the aim of helping one another through direct and rational discussion. + +## Article Organisation + +The article is divided into seven chronological sections, following every phase of the work I have done: + +* **Detecting the problem** +* **Quantifying the problem** +* **Evaluating competitors and their approach to the problem** +* **Developing the first prototype** +* **Throwing everything away and starting again** +* **Getting the first subscription** +* **How to move forward** + +The SaaS product I built is [Inspector](https://www.inspector.dev/), a real-time monitoring tool that helps software developers to avoid losing customers and money due to technical problems in their applications. + +## Detecting the problem + +![Photo by Pixabay from [Pexels](https://www.pexels.com/photo/black-samsung-tablet-computer-106344/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)](https://cdn-images-1.medium.com/max/10368/1*qWDI9beoYFfBmghh-Z6ybQ.jpeg) + +Spending the last 10 years working with software development teams made me realize how complicated it is for developers to handle technical problems that affect applications every day. Development teams have close relationships with their customers, and this is a high risk for companies that produce software because with problems you realize how fragile this bond really is. + +Users do not like problems! It seems obvious, but this aspect is constantly underestimated. This is an uncomfortable truth. No one likes to be in trouble, and it is instinctive to minimize the problem. But by denying this reality you could annoy the customer even more, to the point where they may even reconsider whether or not they “should” even pay you. + +Customers do not spend their time reporting problems and application errors. No one cares about helping us resolve bugs. They just leave our application, and it may be years before we see them again. Despite this, every team I have worked with used the best-known method of figuring out whether applications were working properly or not: + +> “If an angry customers calls you, the software is not working.” + +It is not exactly a technological solution… + +Maybe it seems ridiculous, but beyond the perception tycoons of technology project on our jobs, insiders know that urgency, limited budget, pressing customers, managers, forcing developers to constantly work under pressure, and adopting Band-Aid solutions (to temporarily fix a problem), are survival strategies. Working this approach for 10 years helped me realize there is clearly a problem here. + +## Quantifying the problem + +![Photo by Pixabay from [Pexels](https://www.pexels.com/photo/black-samsung-tablet-computer-106344/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)](https://cdn-images-1.medium.com/max/10000/1*aOZ8uDUscHo0oRipuqKPBg.jpeg) + +At the beginning of 2019, I had just finished some important projects and was expecting to enjoy a little period of calmness. During the last few years, I have used such moments to look for business opportunities which allow me to put my technical skills to good use with the hope of finding the right conditions to launch my own business idea. + +I knew from my experience as a developer that an easy and immediate monitoring instrument would be enough to help development teams to stay up-to-date about the performance of applications, instead of relying on customer calls to know when the software was creating problems. On the other hand, I did not need a tool to monitor everything, as everything often means nothing. And I didn’t want it to be complicated — I did not want to spend a month learning how it worked or need to hire expert engineers just for this job. My life had to be made easier than before. It was necessary to have a ready-to-go developer tool. + +The first step was to understand if there already were solutions trying to solve this problem, so I googled “**application monitoring**” and 941,000,000 results appeared: + +![](https://cdn-images-1.medium.com/max/2022/1*i2ZmDlt4rYw7d90QgEWwzA.png) + +Wow. That’s a very huge amount of content for a problem that probably is huge. But how huge, exactly? + +Software development team inefficiency is a problem I have always faced directly, but there is a big difference between estimating a job task and quantifying the economic impact of a problem. It is even more difficult on a large scale. This tweet captured my attention: + +![](https://cdn-images-1.medium.com/max/2000/1*TvugFYsVbZuQSD_iFwWBTw.png) + +**50% of developers admit to spending up to 50% of their time verifying that applications are working.** + +Software development is work mostly paid by the time technicians spend working on a project, and if there are periods in which developers spend 50 percent of their time checking that everything is okay, a tool which +completely automates this job could be useful enough to buy. + +**So why aren’t they so common to so many developers?** + +## Evaluating competitors and their approach to the problem + +![Photo by [Startup Stock Photos](https://www.pexels.com/@startup-stock-photos?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels) from [Pexels](https://www.pexels.com/photo/man-wearing-black-and-white-stripe-shirt-looking-at-white-printer-papers-on-the-wall-212286/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)](https://cdn-images-1.medium.com/max/10944/1*N-6vdg-eP1w_5Femejg5pA.jpeg) + +I thought about the two main parameters a company looks at when it has to decide which tools to use to increase productivity: + +1. **Simplicity** (ease of installation and use) +2. **Efficacy** (I spend x to solve a problem which is worth x+10, so I gain the +10) + +Using these parameters, I spent about a week creating an evaluation sheet of the most well-known monitoring instruments and I placed them in a graphic: + +![](https://cdn-images-1.medium.com/max/3682/1*jCKXywhfbMjCF98suUklJg.png) + +After days of putting information together, a look at the graphic was enough to realize where the problem was. Easy instruments do not provide enough value to the majority of developers. More complete instruments, instead, are thought of as being for big organizations and requiring skilled staff who dedicate themselves to their installation, configuration, and use, which ends up complicating team operations rather than simplifying them. + +**In my vision, the problem is not the monitoring itself but the development of team efficiency.** + +For a massive adoption, it would be necessary to have a product that requires a minute for the installation, no configurations, and, at the same time, that provides complete and easy information to consult that would allow even medium-sized development teams to fix the real-time monitoring problem. + +And of course, it has to be cool. + +![](https://cdn-images-1.medium.com/max/3682/1*v3O5TbWyrm1P1zC4IeM9TQ.png) + +## Developing the first prototype + +Finally, I decided to try it. The last work experience had gone well and I thought that it would not be impossible for me to create this tool. So, I immediately informed my partners that I wanted to build an MVP for the following two or three months. When I explained it to them, it was hard to make them understand the problem because they are not technicians involved at the same level I am. They gave me the okay based 90 percent on trust, and I thank them for this. + +Over the course of three months I was able to create this prototype: + +![](https://cdn-images-1.medium.com/max/2000/1*KgapMnraeCMY4GqmriE_fw.gif) + +While working on the implementation, I gradually understood the problems of realizing this kind of tool and even problems users would encounter during its use. From a technical point of view, a monitoring product has to be designed to work with huge quantities of data and I also wanted to deal with these data in real-time. + +I had to spend longer than I predicted for the backend part — in other words, the part which cannot be seen, or the backstage of an in-cloud software — leaving out the graphic interface (as you can see above), which is the part users see and use. + +## Throwing everything away and starting again + +![Photo by [Steve Johnson](https://www.pexels.com/@steve?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels) from [Pexels](https://www.pexels.com/photo/focus-photo-of-yellow-paper-near-trash-can-850216/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)](https://cdn-images-1.medium.com/max/9056/1*bsLtywHzuhiY52OKpFii6Q.jpeg) + +In the last few years, the dream of launching a product on the market pushed me to constantly study and apply marketing strategies that are particularly adept for SaaS software, to different projects (even the failed ones). I started to write articles for my blog with the aim of publishing them on different websites and social media to collect the first feedback. + +Although I wrote horrible content in English with writing mistakes because English is not my mother tongue, I started to receive feedback on my idea which included: + +* I don’t understand what I can do with it. +* How can I install it? +* Why use it rather than XXX? +* And so on… + +It was not easy to be objective while looking at developers’ responses and comments. The emotional reaction could always take over and it was really hard for me to understand where the mistakes were because I am not a sales agent or a seller, but I am a damn good technician. + +## Here are the lessons I learned along the way + +#### Lesson 1 — Selling sucks + +![Photo by Pixabay from [Pexels](https://www.pexels.com/photo/black-samsung-tablet-computer-106344/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)](https://cdn-images-1.medium.com/max/7638/1*4uq5q1_6T7myy2dANraKPA.jpeg) + +Thanks to my technical skills on the matter, I did not need to sell. Rather, I just needed to learn how to communicate the problems I faced every day and how I fixed them with my tools. + +I spent an entire month writing the most important things I knew about the monitoring and application scalability problems and the reasons why I decided to start this project, the difficulties I had been encountering during the development of a product, how I fixed them and moved forward, +code examples, technical guides, my best practices, and more. + +Then I gave everything to [Robin](https://www.fiverr.com/robinoo/professionally-proofread-1000-words?context=recommendation&context_alg=recently_ordered%7Cco&context_referrer=homepage&context_type=gig&mod=rep&pckg_id=1&pos=1&source=recently_and_inspired&tier_selection=recommended&ref_ctx_id=1bf19e6d-aa27-407d-86ea-f8ca268b8131), a Canadian copywriter found on Fiverr, who corrected all the content, including the website text, and polished the writing into native-level English. + +#### Lesson 2 — Insufficient product + +![Photo by [Kate Trifo](https://www.pexels.com/@katetrifo?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels) from [Pexels](https://www.pexels.com/photo/almost-empty-shelf-on-grocery-store-4019401/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)](https://cdn-images-1.medium.com/max/12480/1*UeZnUESwTIC3iLYnCsDdSw.jpeg) + +The fear of leaving out the user interface turned out to be a well-founded fear. What I did was not enough to create the kind of experience I had in my head, so I had to start again. The advantage of this was that I fixed the majority of the technical problems. It is not easy for an engineer to put themselves in a designer’s shoes. + +To improve my design sense I attended two courses about the development of graphic interfaces, read three books on design and user experience, and made direct experiments using VueJs as frontend framework. + +## Lesson 3 — Give it a try despite all doubts + +![Photo by Pixabay from [Pexels](https://www.pexels.com/photo/black-samsung-tablet-computer-106344/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)](https://cdn-images-1.medium.com/max/9216/1*Obd6Tg-u6D_Hf-he9M4LFw.jpeg) + +When we spend our time reading books and watching videos on marketing and business, we always learn common advice which, in practical situations, usually does not work. Consider, for example, the mantra: “If you wait until everything is ready, you will never start your business”. **So true**! + +But first experiences push us to emotional reactions that can put us on the defensive. This is a very big mistake because creating a product that is worthy of being bought thanks to its utility is a process, not a single event. The word “launch” misleads us, so forget about it and concentrate on “creation,” the process which, step by step, helps you understand what you need to change and improve to achieve your aim, compared with the outside world. + +## Inspector is born! + +It took me another two months of working on the project; during these months I decided to recreate the brand from scratch, trying to use the prototype experience not just in terms of product but also in terms of marketing and communication. + +[Inspector](https://www.inspector.dev/) — Identify issues in your code before users are aware of them, in less than one minute! + +![](https://cdn-images-1.medium.com/max/3072/1*7tXxOtkvca_518-3LPNEQA.png) + +We endlessly repeated ourselves that the aim was not the monitoring itself, but to help developers automate their working processes: + +* **Without any effort** +* **Without wasting time with manual procedures** +* **Guaranteeing flexibility when it is necessary** + +## Getting the first subscription + +![Photo by [Malte Luk](https://www.pexels.com/@maltelu?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels) from [Pexels](https://www.pexels.com/photo/selective-focus-photography-of-spark-1234390/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)](https://cdn-images-1.medium.com/max/8544/1*ZYIMfCsYF84XptBZ0v68lw.jpeg) + +On July 14, 2019, one of my technical guides was approved to be published on Laravel News, one of the most important websites for the Laravel developers’ community, which got the word out about this tool to an extended audience. Within four days, more than 2,000 people visited the Inspector website, almost 100 companies signed up, and the two first users — from Holland and Switzerland — subscribed. + +I cannot describe the emotion I felt when I received my first notice from Stripe which informed me that I had just received my first payment. When it happened, it was late in the evening. It felt like I was carrying an elephant on my shoulders for seven months and, finally, it went away and let me +breathe. + +A lot of problems cropped up during the following months, and they required time, effort, and money to be fixed. This included things like hacking attacks and overloaded infrastructures, problems that forced me to stay chained to the PC even on Christmas Eve. These are problems that send you crashing back to earth and make you realize things are even more difficult than before because you have something to lose now. + +With the first subscribers, I knew I had an interesting product and, thanks to the web and the cloud, I had the chance to make the product known to developers from all over the world. So I kept working hard, full-time, every day for months to create and publish technical articles from my +experience on as many channels as possible: + +* [https://medium.com/inspector](https://medium.com/inspector) +* [https://dev.to/inspector](https://dev.to/inspector) +* [https://valerio.hashnode.dev/](https://valerio.hashnode.dev/) +* [https://www.facebook.com/inspector.realtime](https://www.facebook.com/inspector.realtime) +* [https://www.linkedin.com/company/14054676](https://www.linkedin.com/company/14054676) +* [https://twitter.com/Inspector_rt](https://twitter.com/Inspector_rt) +* [https://www.indiehackers.com/product/inspector](https://www.indiehackers.com/product/inspector) +* [https://www.reddit.com/r/InspectorFans/](https://www.reddit.com/r/InspectorFans/) +* [https://www.producthunt.com/posts/inspector](https://www.producthunt.com/posts/inspector) + +Now, more than 800 companies and business people have tried Inspector and we have more than 20 active subscriptions now from Singapore, Germany, NewYork, France, Hong Kong, Holland, and more. + +## How to move forward + +Sharing and comparing with others has been fundamental in my journey to get here, so I plan to keep going this way. After all, I’m aware there are still a lot of things we need to improve and, even worse, problems that at the moment we’re ignoring entirely. This is why we started to tell this story during the most important Italian events where the topic is innovation. + +Now we are part of the [**Hubble**](https://www.inspector.dev/inspector-joins-nana-bianca-the-italian-startup-accelerator/) program, the Italian startup accelerator made by Paolo Barberis, Jacopo Marello, and Alessandro Sordi; three of Dada’s founders who spent 20 years of their lives collaborating to finance and support more than 30 Italian and foreign companies in growth. + +Our aim is to introduce ourselves to other managers, business people, and marketing and technology experts in order to elevate the product to the next level and test new instruments and strategies to get Inspector known all over the world. We would like to help software developers to work in a more productive and fun way thanks to smart tools which give them more free time to spend on more valuable activities instead of boring, repetitive, manual checks. + +## Conclusion + +In this article, I’ve shared the mental and practical process that led me to create and launch a SaaS product from scratch, and how I gained my first paying customers. Without sharing my journey I would never have created [Inspector](https://www.inspector.dev), so thank you for reading this article and I invite you to leave a comment below with any questions, curiosities, or just to tell me what you think about it. And if you think this article could be useful to others, please share it on your social media! + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](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)。 From 457417335fc4cdf5eac091aaf8dfa3e126117688 Mon Sep 17 00:00:00 2001 From: lsvih Date: Fri, 31 Jul 2020 14:25:42 +0800 Subject: [PATCH 02/10] Create code-coverage-vue-cypress.md --- article/2020/code-coverage-vue-cypress.md | 326 ++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 article/2020/code-coverage-vue-cypress.md diff --git a/article/2020/code-coverage-vue-cypress.md b/article/2020/code-coverage-vue-cypress.md new file mode 100644 index 00000000000..39266eb4c7b --- /dev/null +++ b/article/2020/code-coverage-vue-cypress.md @@ -0,0 +1,326 @@ +> * 原文地址:[Code Coverage for Vue Applications](https://vuejsdevelopers.com/2020/07/20/code-coverage-vue-cypress/) +> * 原文作者:[Gleb Bahmutov]() +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/article/2020/code-coverage-vue-cypress.md](https://github.com/xitu/gold-miner/blob/master/article/2020/code-coverage-vue-cypress.md) +> * 译者: +> * 校对者: + +# Code Coverage for Vue Applications + +Let's take a Vue application scaffolded with [Vue CLI](https://cli.vuejs.org/) like this [bahmutov/vue-calculator](https://github.com/bahmutov/vue-calculator) app. In this blog post, I will show how to instrument the application's source code to collect the code coverage information. We then will use the code coverage reports to guide the end-to-end test writing. + +## The application + +The example application can be found in [bahmutov/vue-calculator](https://github.com/bahmutov/vue-calculator) repo that was forked from [kylbutlr/vue-calculator](https://github.com/kylbutlr/vue-calculator) which used Vue CLI default template during scaffolding. The code is transformed using the following `babel.config.js` file: + +```js +// babel.config.js +module.exports = { + presets: [ + '@vue/app' + ] +} +``` + +When we start the application with `npm run serve`, we execute the NPM script + +```json +{ + "scripts": { + "serve": "vue-cli-service serve" + } +} +``` + +The application runs at port 8080 by default. + +![Vue calculator application](/images/posts/code_coverage/calculator.png) + +Tada! You can calculate anything you want. + +## Instrument source code + +We can instrument the application code by adding the `plugins` list to the exported Babel config.The plugins should include the [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul). + +```js +// babel.config.js +module.exports = { + presets: [ + '@vue/app' + ], + plugins: [ + 'babel-plugin-istanbul' + ] +} +``` + +The application runs, and now we should find the `window.__coverage__` object with counters for every statement, every function, and every branch of every file. + +![Application coverage object](/images/posts/code_coverage/coverage.png) + +Except the coverage object as shown above, includes only a single entry `src/main.js`, and the coverage object is missing both `src/App.vue` and `src/components/Calculator.vue` files. + +Let's tell `babel-plugin-istanbul` that we want to instrument both `.js` and `.vue` files. + +```js +// babel.config.js +module.exports = { + presets: [ + '@vue/app' + ], + plugins: [ + ['babel-plugin-istanbul', { + extension: ['.js', '.vue'] + }] + ] +} +``` + +**Tip:** we can place `istanbul` settings in a separate file `.nycrc`, or add them to `package.json`. For now, let's just keep these settings together with the plugin itself. + +When we restart the application, we get a new `window.__coverage__` object with entries for `.js` and for `.vue` files. + +![Instrumented JS and Vue files](/images/posts/code_coverage/vue-covered.png) + +## Conditional instrumentation + +If you look at the application's bundle, you will see what the instrumentation does. It inserts counters around every statement, keeping track how many times a statement was executed. There are separate counters for every function and every branch path. + +![Instrumented source code](/images/posts/code_coverage/instrumented.png) + +We do not want to instrument the production code. Let's only instrument the code when `NODE_ENV=test` since we will use the collected code coverage to help us write better tests. + +```js +// babel.config.js +const plugins = [] +if (process.env.NODE_ENV === 'test') { + plugins.push([ + "babel-plugin-istanbul", { + // specify some options for NYC instrumentation here + // like tell it to instrument both JavaScript and Vue files + extension: ['.js', '.vue'], + } + ]) +} +module.exports = { + presets: [ + '@vue/app' + ], + plugins +} +``` + +We can start the application with instrumentation by setting the environment variable. + +```shell +$ NODE_ENV=test npm run serve +``` + +**Tip:** for cross-platform portability use the [cross-env](https://github.com/kentcdodds/cross-env) utility to set an environment variable. + +## End-to-end Tests + +Now that we have instrumented our source code, let us use it to guide us in writing tests. I will install Cypress Test Runner using the official Vue CLI plugin [@vue/cli-plugin-e2e-cypress](https://cli.vuejs.org/core-plugins/e2e-cypress.html). Then I will install the [Cypress code coverage plugin](https://github.com/cypress-io/code-coverage) that will convert the coverage objects into human- and machine-readable reports at the end of the test run. + +```shell +$ vue add e2e-cypress +$ npm i -D @cypress/code-coverage ++ @cypress/code-coverage@3.8.1 +``` + +The [@vue/cli-plugin-e2e-cypress](https://cli.vuejs.org/core-plugins/e2e-cypress.html) has created folder `tests/e2e` where I can load the code coverage plugin from both the support and the plugins files. + +```js +// file tests/e2e/support/index.js +import '@cypress/code-coverage/support' + +// file tests/e2e/plugins/index.js +module.exports = (on, config) => { + require('@cypress/code-coverage/task')(on, config) + // IMPORTANT to return the config object + // with the any changed environment variables + return config +} +``` + +Let's set the environment variable `NODE_ENV=test` to the NPM script command `test:e2e` inserted into `package.json` by the [@vue/cli-plugin-e2e-cypress](https://cli.vuejs.org/core-plugins/e2e-cypress.html). + +```json +{ + "scripts": { + "serve": "vue-cli-service serve", + "build": "vue-cli-service build", + "test:e2e": "NODE_ENV=test vue-cli-service test:e2e" + } +} +``` + +We can place our first end-to-end spec file in `tests/e2e/integration` folder + +```js +/// +describe('Calculator', () => { + beforeEach(() => { + cy.visit('/') + }) + it('computes', () => { + cy.contains('.button', 2).click() + cy.contains('.button', 3).click() + cy.contains('.operator', '+').click() + cy.contains('.button', 1).click() + cy.contains('.button', 9).click() + cy.contains('.operator', '=').click() + cy.contains('.display', 42) + cy.log('**division**') + cy.contains('.operator', '÷').click() + cy.contains('.button', 2).click() + cy.contains('.operator', '=').click() + cy.contains('.display', 21) + }) +}) +``` + +Locally, I will use `npm run test:e2e` command to start the application and open Cypress. The above test passes quickly. Our calculator seems to add and divide numbers just fine. + +![Calculator test](/images/posts/code_coverage/calculator.gif) + +The code coverage plugin automatically generates code coverage reports at the end of the run, as you can see from the messages in the Command Log on the left of the Test Runner. The reports are stored in the folder `coverage`, and by default there are several output formats. + +```text +coverage/ + lcov-report/ + index.html # human HTML report + ... + clover.xml # coverage report for Clover Jenkins reporter + coverage-final.json # plain JSON output for reporting + lcov.info # line coverage report + # for 3rd party reporting services +``` + +While working with tests locally, I prefer opening the HTML coverage report + +```shell +$ open coverage/lcov-report/index.html +``` + +The `index.html` is a static page that shows a table for each source folder with coverage information. + +![Coverage report](/images/posts/code_coverage/coverage-report.png) + +**Tip:** store the entire `coverage/lcov-report` folder as a test artifact on your Continuous Integration (CI) server. Then browse or download the report to see the collected code coverage after the test run. + +End-to-end tests are **effective**. With a single test that loads and interacts with the entire application we have covered 60% of the source code. Even better, by drilling down to the individual files, we discover in `src/components/Calculator.vue` the features we have not tested yet. + +![Covered lines in Calculator.vue file](/images/posts/code_coverage/covered-lines.png) + +The source lines highlighted in red are the lines missed by the test. We can see that we still need to write a test that clears the current number, changes the sign, sets the decimal point, multiplies, etc. But we did test entering and dividing numbers. The test writing thus becomes following the code coverage as a guide to writing end-to-end; add tests until you hit all lines marked in red! + +``` + Calculator + ✓ computes adds and divides (1031ms) + ✓ multiplies, resets and subtracts (755ms) + ✓ changes sign (323ms) + ✓ % operator (246ms) +``` + +As we write more tests we quickly gain coverage and confidence in our application. In the last test we will cover the `decimal () { ... }` method that remained red so far. + +![Decimal method without any coverage](/images/posts/code_coverage/decimal.png) + +The test below types a single digit number and clicks the "." button. The display should show "5.". + +```js +it('decimal', () => { + cy.contains('.button', '5').click() + cy.contains('.button', '.').click() + cy.contains('.display', '5.') +}) +``` + +Hmm, this is weird, the test fails. + +![Decimal test fails](/images/posts/code_coverage/decimal-fails.png) + +A power of Cypress test is that it runs in the real browser. Let's debug the failing test. Put a breakpoint in the `src/components/Calculator.vue` + +```js +decimal() { + debugger + if (this.display.indexOf(".") === -1) { + this.append("."); + } +}, +``` + +Open the DevTools in the browser and run the test again. It will run until it hits the `debugger` keyword in the application code. + +![Debugging decimal method](/images/posts/code_coverage/debugger.png) + +Ohh, the `this.display` is a Number, not a String. Thus `.indexOf()` does not exist and the expression `this.display.indexOf(".")` throws an error. + +**Tip:** if you want Cypress tests to fail any time Vue catches an error, set the following in your code application code: + +```js +// exclude these lines from code coverage +/* istanbul ignore next */ +if (window.Cypress) { + // send any errors caught by the Vue handler + // to the Cypress top level error handler to fail the test + // https://github.com/cypress-io/cypress/issues/7910 + Vue.config.errorHandler = window.top.onerror +} +``` + +Let's fix the logical error in our code: + +```js +decimal() { + if (String(this.display).indexOf(".") === -1) { + this.append("."); + } +}, +``` + +The test passes. Now the code coverage report tells us that the "Else" path of the condition has not been taken yet. + +![Else path not taken](/images/posts/code_coverage/decimal-else.png) + +Extend the test to click the "." operator twice during the test and it will cover all code paths and turn the entire method coverage green. + +```js +it('decimal', () => { + cy.contains('.button', '5').click() + cy.contains('.button', '.').click() + cy.contains('.display', '5.') + cy.log('**does not add it twice**') + cy.contains('.button', '.').click() + cy.contains('.display', '5.') +}) +``` + +![Decimal test passes](/images/posts/code_coverage/decimal-test-passes.png) + +![All code paths covered](/images/posts/code_coverage/decimal-covered.png) + +Now let's run all tests again. All tests pass in less than 3 seconds + +![All tests passing](/images/posts/code_coverage/all-tests.gif) + +And the tests together cover our entire code base. + +![Full code coverage](/images/posts/code_coverage/full-cover.png) + +## Conclusions + +* adding code instrumentation to Vue projects is simple if the project is already using Babel to transpile the source code. By adding the `babel-plugin-istanbul` to the list of plugins you get the code coverage information under `window.__coverage__` object. +* you probably want to only instrument the source code while running tests to avoid slowing down the production build +* end-to-end tests are very effective at covering a lot of code because they exercise the full application. +* the code coverage reports produced by `@cypress/code-coverage` plugin can guide you in writing tests to ensure all features are tested + +For more information read the [Cypress code coverage guide](https://on.cypress.io/code-coverage) and [@cypress/code-coverage](https://github.com/cypress-io/code-coverage) documentation. + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](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)。 From bc158db3750b34a4fca8233ff46fc705d75c5e5c Mon Sep 17 00:00:00 2001 From: lsvih Date: Fri, 31 Jul 2020 14:29:24 +0800 Subject: [PATCH 03/10] Update code-coverage-vue-cypress.md (#7255) --- article/2020/code-coverage-vue-cypress.md | 32 ++++++++++++----------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/article/2020/code-coverage-vue-cypress.md b/article/2020/code-coverage-vue-cypress.md index 39266eb4c7b..565b3396a99 100644 --- a/article/2020/code-coverage-vue-cypress.md +++ b/article/2020/code-coverage-vue-cypress.md @@ -7,6 +7,8 @@ # Code Coverage for Vue Applications +![](https://vuejsdevelopers.com/images/posts/versions/code_coverage_1200.webp) + Let's take a Vue application scaffolded with [Vue CLI](https://cli.vuejs.org/) like this [bahmutov/vue-calculator](https://github.com/bahmutov/vue-calculator) app. In this blog post, I will show how to instrument the application's source code to collect the code coverage information. We then will use the code coverage reports to guide the end-to-end test writing. ## The application @@ -34,7 +36,7 @@ When we start the application with `npm run serve`, we execute the NPM script The application runs at port 8080 by default. -![Vue calculator application](/images/posts/code_coverage/calculator.png) +![Vue calculator application](https://vuejsdevelopers.com/images/posts/code_coverage/calculator.png) Tada! You can calculate anything you want. @@ -56,7 +58,7 @@ module.exports = { The application runs, and now we should find the `window.__coverage__` object with counters for every statement, every function, and every branch of every file. -![Application coverage object](/images/posts/code_coverage/coverage.png) +![Application coverage object](https://vuejsdevelopers.com/images/posts/code_coverage/coverage.png) Except the coverage object as shown above, includes only a single entry `src/main.js`, and the coverage object is missing both `src/App.vue` and `src/components/Calculator.vue` files. @@ -80,13 +82,13 @@ module.exports = { When we restart the application, we get a new `window.__coverage__` object with entries for `.js` and for `.vue` files. -![Instrumented JS and Vue files](/images/posts/code_coverage/vue-covered.png) +![Instrumented JS and Vue files](https://vuejsdevelopers.com/images/posts/code_coverage/vue-covered.png) ## Conditional instrumentation If you look at the application's bundle, you will see what the instrumentation does. It inserts counters around every statement, keeping track how many times a statement was executed. There are separate counters for every function and every branch path. -![Instrumented source code](/images/posts/code_coverage/instrumented.png) +![Instrumented source code](https://vuejsdevelopers.com/images/posts/code_coverage/instrumented.png) We do not want to instrument the production code. Let's only instrument the code when `NODE_ENV=test` since we will use the collected code coverage to help us write better tests. @@ -182,7 +184,7 @@ describe('Calculator', () => { Locally, I will use `npm run test:e2e` command to start the application and open Cypress. The above test passes quickly. Our calculator seems to add and divide numbers just fine. -![Calculator test](/images/posts/code_coverage/calculator.gif) +![Calculator test](https://vuejsdevelopers.com/images/posts/code_coverage/calculator.gif) The code coverage plugin automatically generates code coverage reports at the end of the run, as you can see from the messages in the Command Log on the left of the Test Runner. The reports are stored in the folder `coverage`, and by default there are several output formats. @@ -205,13 +207,13 @@ $ open coverage/lcov-report/index.html The `index.html` is a static page that shows a table for each source folder with coverage information. -![Coverage report](/images/posts/code_coverage/coverage-report.png) +![Coverage report](https://vuejsdevelopers.com/images/posts/code_coverage/coverage-report.png) **Tip:** store the entire `coverage/lcov-report` folder as a test artifact on your Continuous Integration (CI) server. Then browse or download the report to see the collected code coverage after the test run. End-to-end tests are **effective**. With a single test that loads and interacts with the entire application we have covered 60% of the source code. Even better, by drilling down to the individual files, we discover in `src/components/Calculator.vue` the features we have not tested yet. -![Covered lines in Calculator.vue file](/images/posts/code_coverage/covered-lines.png) +![Covered lines in Calculator.vue file](https://vuejsdevelopers.com/images/posts/code_coverage/covered-lines.png) The source lines highlighted in red are the lines missed by the test. We can see that we still need to write a test that clears the current number, changes the sign, sets the decimal point, multiplies, etc. But we did test entering and dividing numbers. The test writing thus becomes following the code coverage as a guide to writing end-to-end; add tests until you hit all lines marked in red! @@ -225,7 +227,7 @@ The source lines highlighted in red are the lines missed by the test. We can see As we write more tests we quickly gain coverage and confidence in our application. In the last test we will cover the `decimal () { ... }` method that remained red so far. -![Decimal method without any coverage](/images/posts/code_coverage/decimal.png) +![Decimal method without any coverage](https://vuejsdevelopers.com/images/posts/code_coverage/decimal.png) The test below types a single digit number and clicks the "." button. The display should show "5.". @@ -239,7 +241,7 @@ it('decimal', () => { Hmm, this is weird, the test fails. -![Decimal test fails](/images/posts/code_coverage/decimal-fails.png) +![Decimal test fails](https://vuejsdevelopers.com/images/posts/code_coverage/decimal-fails.png) A power of Cypress test is that it runs in the real browser. Let's debug the failing test. Put a breakpoint in the `src/components/Calculator.vue` @@ -254,7 +256,7 @@ decimal() { Open the DevTools in the browser and run the test again. It will run until it hits the `debugger` keyword in the application code. -![Debugging decimal method](/images/posts/code_coverage/debugger.png) +![Debugging decimal method](https://vuejsdevelopers.com/images/posts/code_coverage/debugger.png) Ohh, the `this.display` is a Number, not a String. Thus `.indexOf()` does not exist and the expression `this.display.indexOf(".")` throws an error. @@ -283,7 +285,7 @@ decimal() { The test passes. Now the code coverage report tells us that the "Else" path of the condition has not been taken yet. -![Else path not taken](/images/posts/code_coverage/decimal-else.png) +![Else path not taken](https://vuejsdevelopers.com/images/posts/code_coverage/decimal-else.png) Extend the test to click the "." operator twice during the test and it will cover all code paths and turn the entire method coverage green. @@ -298,17 +300,17 @@ it('decimal', () => { }) ``` -![Decimal test passes](/images/posts/code_coverage/decimal-test-passes.png) +![Decimal test passes](https://vuejsdevelopers.com/images/posts/code_coverage/decimal-test-passes.png) -![All code paths covered](/images/posts/code_coverage/decimal-covered.png) +![All code paths covered](https://vuejsdevelopers.com/images/posts/code_coverage/decimal-covered.png) Now let's run all tests again. All tests pass in less than 3 seconds -![All tests passing](/images/posts/code_coverage/all-tests.gif) +![All tests passing](https://vuejsdevelopers.com/images/posts/code_coverage/all-tests.gif) And the tests together cover our entire code base. -![Full code coverage](/images/posts/code_coverage/full-cover.png) +![Full code coverage](https://vuejsdevelopers.com/images/posts/code_coverage/full-cover.png) ## Conclusions From 4c3a02e15f8827febc30052deca6b7ed0508a250 Mon Sep 17 00:00:00 2001 From: niayyy Date: Mon, 3 Aug 2020 20:12:38 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E5=A6=82=E4=BD=95=E5=9C=A8=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=E4=B8=AD=E9=9A=90=E8=97=8F=E7=A7=98=E5=AF=86?= =?UTF-8?q?=20=E2=80=94=E2=80=94=20JavaScript=20=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E7=8E=B0=E4=BB=A3=E6=96=87=E6=9C=AC=E9=9A=90=E8=97=8F=20(#7254?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update how-to-hide-secrets-in-strings-modern-text-hiding-in-javascript.md * Update how-to-hide-secrets-in-strings-modern-text-hiding-in-javascript.md * Update how-to-hide-secrets-in-strings-modern-text-hiding-in-javascript.md * Update how-to-hide-secrets-in-strings-modern-text-hiding-in-javascript.md --- ...trings-modern-text-hiding-in-javascript.md | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/article/2020/how-to-hide-secrets-in-strings-modern-text-hiding-in-javascript.md b/article/2020/how-to-hide-secrets-in-strings-modern-text-hiding-in-javascript.md index 6cc069f9926..96b53f9319b 100644 --- a/article/2020/how-to-hide-secrets-in-strings-modern-text-hiding-in-javascript.md +++ b/article/2020/how-to-hide-secrets-in-strings-modern-text-hiding-in-javascript.md @@ -2,143 +2,143 @@ > * 原文作者:[Mohan Sundar](https://medium.com/@itsmohanpierce) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/article/2020/how-to-hide-secrets-in-strings-modern-text-hiding-in-javascript.md](https://github.com/xitu/gold-miner/blob/master/article/2020/how-to-hide-secrets-in-strings-modern-text-hiding-in-javascript.md) -> * 译者: -> * 校对者: +> * 译者:[niayyy](https://github.com/nia3y) +> * 校对者:[FateZeros](https://github.com/FateZeros)、[zenblo](https://github.com/zenblo) -# How to Hide Secrets in Strings — Modern Text hiding in JavaScript +# 如何在字符串中隐藏秘密 —— JavaScript 中的现代文本隐藏 ![**All Hallows’ Eve — Illustrated by [Kaiseir](https://dribbble.com/kaiseir)**](https://cdn-images-1.medium.com/max/2000/1*HOGI5cdt1MJ9MBo1qTc1zA.jpeg) -> Sometimes the best hiding place is the one that’s in plain sight. +> 最危险的地方就是最安全的地方。 -If you were a spy in a hostile country, merely sending a message back to the US would be incriminating. If that message was encrypted, it’d probably be a whole bunch more incriminating, and things would only get worse when you, the spy, refused to decrypt the message for the authorities. **Steganography**, which literally means “hidden writing”, is about hiding the existence of a message. +如果你是一个在敌对国家的间谍,那么仅向美国发送信息便是犯罪。如果消息是加密的,那可能导致更大的罪名,当你拒绝为当局解密消息时,情况会变得更糟。**隐写术**,字面意思是“隐藏的文字”,是指隐藏消息的存在。 -Chet Hosmer, founder of [**Python Forensics**](http://python-forensics.org/) pointed out that +[**Python Forensics**](http://python-forensics.org/) 的创始人 Chet Hosmer 指出: -> Steganography hides the mere existence of the communication. Unlike its cousin cryptography, which is easy to detect but difficult to break, steganography provides the most interesting element of all ‘To Hide in Plain sight’. +> 隐写术掩盖了信息的存在。不像它的表亲密码学一样容易检测,难以破解,隐写术提供最有趣的特点在于“藏在众目睽睽之下”。(Steganography hides the mere existence of the communication. Unlike its cousin cryptography, which is easy to detect but difficult to break, steganography provides the most interesting element of all ‘To Hide in Plain sight’.) ![Cloak of invisibility](https://cdn-images-1.medium.com/max/2000/1*Ze0Yy0Op7kqT8fJ3Le5FSg.gif) -**Did you know that the steganography quote above, has a hidden secret that is invisible?** Would you have been able to detect its existence if I hadn’t mentioned it? Well, check out the rest of the article, to make sense out of it. +**你是否知道上面的隐写术引用中有一个不为人知的隐藏秘密?** 如果我没有提到它,你是否能够感知它的存在?好吧,请阅读本文的其余部分,以理解其中的意义。 -## Invisible characters in Unicode +## Unicode 中的不可见字符 -Zero Width Characters are non-printing characters, a part of the Unicode table. As the name suggests they don’t even show their presence. They are used to enable line wrapping in long words, joining emojis, combine two characters into a ligature, keep them from joining, etc. +零宽度字符是非打印字符,是 Unicode 表的一部分。顾名思义,他们甚至不展示自己的存在。它们用于使长句换行,连接表情符号,将两个字符连接起来,防止它们结合等。 ![The characters `zwj` join the emoji’s but they are not visible](https://cdn-images-1.medium.com/max/2000/1*ATrlUYwomSvUtygin5ndIA.png) -These characters have increasingly found their way in-text hiding, their complete invisibility being a remarkable selling point. They cannot be blocked as they are integral in multiple languages and emojis. And it also turns out that ZWCs aren’t the only characters which are invisible, eg. Invisible separator — U+2063. +这些字符越来越多地发现其隐藏在文本中的方式,它们的完全隐形性是一个了不起的特点。由于它们在多种语言和表情符号中不可或缺,因此无法屏蔽它们。而且,事实证明,ZWC 并不是唯一不可见的字符。例如:隐形的分隔符 —— U+2063。 ![The table that contains mostly used invisible characters](https://cdn-images-1.medium.com/max/2000/1*eP3yPonDN-Px68R1gO0PXw.png) -One small problem with this table tho! Gmail blocks U+200B ( Zero width space ). Not to mention, Twitter is known for blacklisting unnecessary invisible characters, none of the characters in the table except U+200C, U+200D and U+180e works. So we now have three characters! +但是这个表格有一个小问题,Gmail 会屏蔽 U+200B(零宽度空格)。更不用说其它软件,Twitter 会将不必要的不可见字符列入黑名单,除 U+200C,U+200D 和 U+180e 之外,表中的所有字符均不起作用。因此,现在我们有了三个字符! ![](https://cdn-images-1.medium.com/max/2160/1*pnl_e3gWWQ3z1l58LTxaBg.jpeg) -Oh, wait, U+180e is not invisible and renders weirdly in iOS devices. We are now down to only 2 characters. +哦,等等,U+180e 不是不可见的,并且在 iOS 设备中呈现异常。现在我们只有 2 个字符。 -So we tore apart the Unicode table and started to test each possible Invisible character for its cross-platform / web invisibility. Fortunately, we were able to add 4 more characters to our arsenal, a total of 6 invisible characters that we can now use to hide our secrets in strings. All set! Ready to strike ..! +因此,我们打开 Unicode 表,并开始测试每个可能的不可见字符是否具有跨平台 / Web 可见性。幸运的是,我们能够在表格中再添加 4 个字符,总共 6 个不可见字符,我们现在可以使用它们在字符串中的隐藏秘密。可以了,好了!准备战斗..! ![Assemble!](https://cdn-images-1.medium.com/max/2000/1*HSPg4C9SGIT9-O6GWK0img.gif) -## What is StegCloak and how it works? +## StegCloak 是什么以及如何工作? -StegCloak is a pure JavaScript steganography module that can be used to hide secrets inside plain text after going through two layers of maximum possible compression and a layer of encryption. So not only does it cloak the secret, but it also protects it with a password of your choice along with an array of other features. **[Check out our demo here](https://www.youtube.com/watch?v=RBDqZwcGvQk).** +StegCloak 是一个纯 JavaScript 的隐写模块,在经过两层最大可能压缩和一层加密后,可用于在纯文本中隐藏秘密。因此,它不仅掩盖了秘密,而且还通过你选择的密码以及一系列其他特性来进行保护。**[在这查看我们的演示](https://www.youtube.com/watch?v=RBDqZwcGvQk)。** -#### Hide +#### 隐藏 ![Hiding secrets in tweets](https://cdn-images-1.medium.com/max/2000/1*i-woBuZ902ZSMsrj9xSnoA.gif) -#### Reveal +#### 显示 ![Extracting hidden secret from the tweet](https://cdn-images-1.medium.com/max/2000/1*DqpMYkBY5NUdbw5wUKXliw.gif) -#### A brief idea of how StegCloak hides your secrets and compresses it +#### StegCloak 如何隐藏你的秘密并将其压缩的简要说明 -Step 1: **Compress and Encrypt** the secret. +步骤 1:**压缩并加密**秘密。 ![](https://cdn-images-1.medium.com/max/2000/1*ALhxrbOw6UBJf858ckg9ew.png) -Security never played a role in these kinds of “hacks” and with StegCloak we wanted it to satisfy [**Kerckhoff’s principle](https://en.wikipedia.org/wiki/Kerckhoffs%27s_principle)** which states: +安全策略从未在这些系统入侵事件中发挥作用,我们希望借助 StegCloak 来满足 **[Kerckhoff 原则](https://en.wikipedia.org/wiki/Kerckhoffs%27s_principle)**,其中指出: -> An ideal crypto-system should be secure even if everything about the system is exposed to the public except the secret key. +> 理想的加密系统应当具备很强的安全保障,能够在除密钥之外的系统全部内容都对外公开的情况下也是安全的。 -Even if the attacker identifies how the algorithm works, it should not be possible to reveal the secret message. +即使攻击者识别出了算法的工作原理,也应该不可能破解秘密消息。 -#### Satisfying the principle +#### 满足原则 ![](https://cdn-images-1.medium.com/max/2000/1*Bqj9PFww4K_VhaWfODqgMg.png) -For this, we need password-based symmetric encryption. Considering human tendencies to use small and weak passwords and also their preference to use the same password multiple times, we decided to derive a strong key from the given password and also increase the randomness of the key by introducing random salts. Randomness in the key is required to prevent attacks based on the analysis of multiple ciphertexts generated with the same key. Now, the usual block cipher modes in AES like ECB or CBC resulted in additional padding of a minimum of 16 bytes block. So to send “Hi” CBC mode pads 0’s to make it 16 in length, and removes them during extraction. This is bad. Therefore, we used the stream cipher mode CTR (padding less cipher)to generate the ciphertext. +为此,我们需要基于密码的对称加密。鉴于人们总是习惯使用简单脆弱的密码,以及他们倾向于多次使用相同的密码,我们决定从给定的密码中得出强密钥,并通过引入随机盐来增加密钥的随机性。根据对使用同一密钥生成的多个密文的分析,密钥需要具有随机性以防止攻击。现在,AES 中的常规块密码模式像 ECB 或 CBC 导致至少填充 16 个字节的块。 比如,发送 “Hi” CBC 模式填充 0 使其长度为 16,并在提取过程中将其删除,但这并不是好方法。因此,我们使用流密码模式 CTR(填充较少的密码)来生成密文。 ![](https://cdn-images-1.medium.com/max/2000/1*b_0-voMOjqM2Jk1EKCZ1Fw.png) -Step 2: **Encode and compress again** with the extra two characters. +第 2 步:使用额外的两个字符**再次编码和压缩**。 ![](https://cdn-images-1.medium.com/max/2000/1*FEstcl9rEF0eX8Q3n0u4pg.png) ![](https://cdn-images-1.medium.com/max/2000/1*HfXp1u543ZaLCC5MaQ05_w.png) -As shown in the above figure, even though we had six ZWC characters only 4 were used as 6 is not a power of 2.The two extra characters (U+2063, U+2064) are used to do an additional layer of abstracted Huffman compression reducing redundancy. After the secret has been converted to ZWCs, the two most repeating ZWCs in the stream are determined, say U+200D and U+200C. Now every two consecutive occurrences of U+200Ds and U+200Cs are replaced with one U+2063 or U+2064. This saves a lot as redundancy was frequently observed. +如上图所示,即使我们有 6 个 ZWC 字符,也只有 4 个被使用,因为 6 不是 2 的幂。两个额外的字符(U+2063,U+2064)被用来做一个额外的抽象霍夫曼层压缩减少冗余。将机密转换为 ZWC 后,将确定流中两个重复最多的 ZWC,例如 U+200D 和 U+200C。现在,每两个连续出现的 U+200D 和 U+200C 被一个 U+2063 或 U+2064 替换。由于经常观察到冗余,因此可以节省很多。 -Step 3: **Embed the invisible stream** to the first space of the cover text. +步骤 3:**将不可见的流嵌入**封面文字的开头。 ![](https://cdn-images-1.medium.com/max/2000/1*23avUCEVPdvmQr62z1eCzw.gif) -`Hi` is now hidden in hello world as 6 characters, so now the total length of this string is +`Hi` 现在隐藏在了 6 个字符的 hello world 中,因此该字符串的总长度为 -10 + 6 = 16 characters +10 + 6 = 16 个字符 -#### Extraction +#### 提取 ![](https://cdn-images-1.medium.com/max/2000/1*19IYY7Rw7rL76YX0NnmL5Q.gif) -Just the vice versa, nothing complicated but given that the payload’s length increases when we add features like encryption and invisibility, we do two layers of compression ( before and after ) to minimize the cost as much as we can. So it’s just a small price to pay for salvation. +反之亦然,没有什么复杂的,但考虑到当我们添加如加密和不可见性之类的特性时,有效载荷的长度会增加,因此我们进行了两层压缩(之前和之后)以最大程度地降低成本。因此,提取只付出很小代价。 ![](https://cdn-images-1.medium.com/max/2000/1*p2dPqMPTmSxW9ndw7OjMKw.gif) -You can at any point of time turn off certain features to reduce the payload length, we designed StegCloak to be flexible to user needs. +你可以在任何时间关闭某些特性以减少有效载荷长度,我们设计了 StegCloak 以灵活满足用户需求。 -## Style of the module +## 模块风格 -> Life is much more easier when you can visualise your functions as a curve in a graph — Kyle simpson +> 当你可以将函数可视化为图形中的曲线时,生活会更加轻松 —— Kyle simpson -StegCloak follows the functional programming paradigm and as a whole consists of only two functions: hide and reveal. These two functions are built using multiple small Lego pieces. These pieces are nothing but Pure functions or Different versions of the same pure function that was curried etc. StegCloak has only one impure function which is `encrypt()` as it generates a random salt for increasing the security of the cipher. +StegCloak 遵循编函数式编程范例,并且总体上仅包含两个功能:隐藏和显示。这两个功能是将许多代码段如堆积木般构建而成。这些片段不过是纯函数或柯里化后相同纯函数的不同版本。StegCloak 仅具有 `encrypt()` 不是纯函数,因为它会生成随机盐来增加密码的安全性。 -#### Flow +#### 流 ![How it works!](https://cdn-images-1.medium.com/max/2000/1*krNVCV3uhVJ2QTHKczM43w.png) -In my perspective, having a functional approach makes your program look more like a flow chart thus increasing its readability. +在我看来,采用函数式方法会使你的程序看起来更像流程图,从而提高其可读性。 ![**Hide( )** in stegcloak.js](https://cdn-images-1.medium.com/max/3940/1*Vn7gxNmZVkVPQqgZuwOIOQ.png) ![**Reveal( )** in stegcloak.js](https://cdn-images-1.medium.com/max/4096/1*aN2AczUqvlXG6XtijJPNfA.png) -StegCloak uses a functional programming library called RamdaJS. The R.Pipe takes in functions and passes the arguments to the first function where its output is given as the input to the next function in the pipe. You can see that the pieces can be proxied to another pipe or operated on before being sent to the next pipe. Readability and point-free style were one of the biggest focus of the design +StegCloak 使用称为 RamdaJS 的函数式编程库。R.Pipe 接受输入函数并将参数传递给第一个函数,然后将其输出作为管道中下一个函数的输入。你会看到,可以将这些代码片段代理到另一个管道或在之前对其进行操作,然后再发送到下一个管道。可读性和隐式编程风格是设计的最大重点之一 -## Unfolding the mystery of the quote +## 揭开引用句的奥秘 -* Copy Chet Hosmer’s quote above and visit [stegcloak.surge.sh](https://stegcloak.surge.sh) -* Type the password — “Aparecium” in Reveal +*复制上面 Chet Hosmer 的引用句,并访问[stegcloak.surge.sh](https://stegcloak.surge.sh) +*在 Reveal 中输入密码 —— “Aparecium” ![](https://cdn-images-1.medium.com/max/2000/1*k5OSMLvm-vZesGylNImJEA.png) -* Paste the copied quote in the STEGCLOAKED MESSAGE text box +* 将复制的句子粘贴到 STEGCLOAKED MESSAGE 文本框中 ![](https://cdn-images-1.medium.com/max/2000/1*9Qd64_Y8acVK4uP9E9e9fg.png) -* Click **GET SECRET** and Voila! +* 单击**获取机密**,看! -## Conclusion +## 结论 -This was built by [myself](https://www.linkedin.com/in/mohan-sundar-9881a7180/) and two of my friends [Jyothishmathi CV](https://www.linkedin.com/in/c-v-jyothishmathi-791578181/), [Kandavel](https://www.linkedin.com/in/ak5123/) +它是由[我](https://www.linkedin.com/in/mohan-sundar-9881a7180/)和我的两个朋友 [Jyothishmathi CV](https://www.linkedin.com/in/c-v-jyothishmathi-791578181/)、[Kandavel](https://www.linkedin.com/in/ak5123/) 建立的 -We hope you enjoy it as much as we did building it! +我们希望你像我们构建它一样喜欢它! -Checkout [StegCloak](https://stegcloak.surge.sh/) in [Github](https://github.com/KuroLabs/stegcloak) or visit [https://stegcloak.surge.sh](https://stegcloak.surge.sh). +在 [Github](https://github.com/KuroLabs/stegcloak) 上搜索 [StegCloak](https://stegcloak.surge.sh/)或访问 [https://stegcloak.surge.sh](https://stegcloak.surge.sh)。 -Thank you for reading this article 🖤. +感谢你阅读本文 🖤。 > 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 From f2ee17a189d78eac49f6aa5c96cb0b883cd2cb9e Mon Sep 17 00:00:00 2001 From: Herman Date: Tue, 4 Aug 2020 10:04:00 +0800 Subject: [PATCH 05/10] =?UTF-8?q?Python=EF=BC=9A=E4=BD=BF=E7=94=A8=20local?= =?UTF-8?q?s()=20=E5=92=8C=20globals()=20=E5=B7=A7=E5=A6=99=E7=BC=96?= =?UTF-8?q?=E7=A8=8B=20(#7240)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Finish translation * Update translator name * Remove original contents * Add reviewer Co-authored-by: loststar Co-authored-by: WANG Zijian Co-authored-by: loststar --- ...hon-smart-coding-with-locals-and-global.md | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/article/2020/python-smart-coding-with-locals-and-global.md b/article/2020/python-smart-coding-with-locals-and-global.md index 69e9539fc04..5f2486f5379 100644 --- a/article/2020/python-smart-coding-with-locals-and-global.md +++ b/article/2020/python-smart-coding-with-locals-and-global.md @@ -1,20 +1,20 @@ -> * 原文地址:[Python: smart coding with locals() and global()](https://medium.com/python-in-plain-english/python-smart-coding-with-locals-and-global-257ae25461ee) +> * 原文地址:[Python: smart coding with locals() and globals()](https://medium.com/python-in-plain-english/python-smart-coding-with-locals-and-global-257ae25461ee) > * 原文作者:[Konstantinos Patronas](https://medium.com/@kpatronas) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/article/2020/python-smart-coding-with-locals-and-global.md](https://github.com/xitu/gold-miner/blob/master/article/2020/python-smart-coding-with-locals-and-global.md) -> * 译者: -> * 校对者: +> * 译者:[Herman](https://github.com/actini) +> * 校对者:[loststar](https://github.com/loststar) -# Python: smart coding with locals() and global() +# Python:使用 locals() 和 globals() 巧妙编程 ![Photo by [Christina @ wocintechchat.com](https://unsplash.com/@wocintechchat?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)](https://cdn-images-1.medium.com/max/12032/0*ZYy01ayW-6rkXMGV) -Coding can be hard! and one of the most common reasons for that is that we tend to solve a coding problem thinking complex ways which can create more problems than solutions. +写代码是一件很困难的事儿!尤其是我们往往把代码问题想得太复杂,导致在解决问题的过程中引发更多的问题。 -**Lets try to break down the following coding problem:** +**我们来试着解决下面这个问题:** -* The first server in servers list is a proxy server, so it needs to use the connect_to_proxy function. -* All other server are just servers that we want to connect and get to the next server, so they need to use the hop_to_server function. +* 列表的第一个服务器是代理服务器,因此它需要调用 `connect_to_proxy` 函数。 +* 其他服务器只提供服务,我们可以使用代理连接到它们,因此需要调用 `hop_to_server` 函数。 ```python def connect_servers(): @@ -28,16 +28,16 @@ def connect_servers(): server_conn = hop_to_server(server=server) ``` -locals() returns the variables / objects declared in the scope a function. So in this case the code will do the following: +`locals()` 会返回在当前函数里声明的变量/对象,因此这段代码是这样工作的: -1. The first time that will run it will check if the “server_conn” variable has been created or not. Since this is the first time running the connect_to_proxy function will be executed and will return its value to the server_conn variable. -2. After the first run of the loop the hop_to_server function will be executed untill the end of the loop. +1. 每次循环执行时,程序都会检查 `server_conn` 变量是否已经存在,因此第一次循环时 `connect_to_proxy` 函数会被执行,并且其返回值将会传递给 `server_conn` 变量。 +2. 之后每次执行的过程中,程序都会调用 `hop_to_server` 函数,直到循环结束。 -But what about the globals() function? well it does the same thing, checks if a variable exists but not only in the scope of a function but globaly. +那么 `globals()` 函数又是干什么的呢?其实它的功能跟 `locals()` 是一样的,都可以用来检查变量是否存在,只不过它的作用域是全局的,而不仅限于当前函数。 -So using the locals() and globals() functions allows writing smart shortcuts, which in turn means less things that can go wrong and makes the code more readable. +因此合理使用 `locals()` 和 `globals()` 函数能写出巧妙的代码,换句话说,代码不容易出错也更具有可读性。 -**Doing the same without using the locals() function:** +**不使用 `locals()` 实现相同的功能** ```python def connect_servers(): @@ -53,7 +53,7 @@ def connect_servers(): server_conn = hop_to_server(server=server) ``` -It will work, but the code has more lines and is not as readable as the first example. +这也奏效,但是代码行数变多了,并且不如第一个例子可读性好。 > 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 From 3cf64da4dedbb52192ec5e021403d7f2c21627b1 Mon Sep 17 00:00:00 2001 From: lsvih Date: Tue, 4 Aug 2020 10:28:35 +0800 Subject: [PATCH 06/10] Create my-favorite-javascript-tips-and-tricks.md (#7267) * Create my-favorite-javascript-tips-and-tricks.md * Update my-favorite-javascript-tips-and-tricks.md * Update my-favorite-javascript-tips-and-tricks.md --- .../my-favorite-javascript-tips-and-tricks.md | 393 ++++++++++++++++++ 1 file changed, 393 insertions(+) create mode 100644 article/2020/my-favorite-javascript-tips-and-tricks.md diff --git a/article/2020/my-favorite-javascript-tips-and-tricks.md b/article/2020/my-favorite-javascript-tips-and-tricks.md new file mode 100644 index 00000000000..1ebf82c0683 --- /dev/null +++ b/article/2020/my-favorite-javascript-tips-and-tricks.md @@ -0,0 +1,393 @@ +> * 原文地址:[My Favorite JavaScript Tips and Tricks](https://blog.greenroots.info/my-favorite-javascript-tips-and-tricks-ckd60i4cq011em8s16uobcelc) +> * 原文作者:[Tapas Adhikary](https://hashnode.com/@atapas) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/article/2020/my-favorite-javascript-tips-and-tricks.md](https://github.com/xitu/gold-miner/blob/master/article/2020/my-favorite-javascript-tips-and-tricks.md) +> * 译者: +> * 校对者: + +# My Favorite JavaScript Tips and Tricks + +![image](https://user-images.githubusercontent.com/5164225/89246151-0dbc1300-d63d-11ea-982e-fa61dd13b7d9.png) + +## Motivation + +Most of the programming languages are open enough to allow programmers doing things multiple ways for the similar outcome. JavaScript is no way different. With JavaScript, we often find multiple ways of doing things for a similar outcome, and that's confusing at times. + +Some of the usages are better than the other alternatives and thus, these are my favorites. I am going to list them here in this article. I am sure, you will find many of these in your list too. + +## 1. Forget string concatenation, use template string(literal) + +Concatenating strings together using the `+` operator to build a meaningful string is old school. Moreover, concatenating strings with dynamic values(or expressions) could lead to frustrations and bugs. + +```js +let name = 'Charlse'; +let place = 'India'; +let isPrime = bit => { + return (bit === 'P' ? 'Prime' : 'Nom-Prime'); +} + +// string concatenation using + operator +let messageConcat = 'Mr. ' + name + ' is from ' + place + '. He is a' + ' ' + isPrime('P') + ' member.' +``` + +Template literals(or Template strings) allow embedding expressions. It has got unique syntax where the string has to be enclosed by the backtick (\`\`). Template string can contain placeholders for dynamic values. These are marked by the dollar sign and curly braces (${expression}). + +Here is an example demonstrating it, + +```js +let name = 'Charlse'; +let place = 'India'; +let isPrime = bit => { + return (bit === 'P' ? 'Prime' : 'Nom-Prime'); +} + +// using template string +let messageTemplateStr = `Mr. ${name} is from ${place}. He is a ${isPrime('P')} member.` +console.log(messageTemplateStr); +``` + +## 2. isInteger + +There is a much cleaner way to know if a value is an integer. The `Number` API of JavaScript provides a method called, `isInteger()` to serve this purpose. It is very useful and better to be aware. + +```js +let mynum = 123; +let mynumStr = "123"; + +console.log(`${mynum} is a number?`, Number.isInteger(mynum)); +console.log(`${mynumStr} is a number?`, Number.isInteger(mynumStr)); +``` + +Output: + +![2.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1595930285107/RiLxixUxC.png?auto=format&q=60) + +## 3. Value as Number + +Have you ever noticed, `event.target.value` always returns a string type value even when the input box is of type number? + +Yes, see the example below. We have a simple text box of type number. It means it accepts only numbers as input. It has an event handler to handle the key-up events. + +```html + +``` + +In the event handler method we take out the value using `event.target.value`. But it returns a string type value. Now I will have additional headache to parse it to an integer. What if the input box accepts floating numbers(like, 16.56)? parseFloat() then? Ah, all sort of confusions and extra work! + +```js +function trackChange(event) { + let value = event.target.value; + console.log(`is ${value} a number?`, Number.isInteger(value)); +} +``` + +Use `event.target.valueAsNumber` instead. It returns the value as number. + +```js +let valueAsNumber = event.target.valueAsNumber; +console.log(`is ${value} a number?`, Number.isInteger(valueAsNumber)); +``` + +![3.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1595935455526/Tv1sEFRxe.png?auto=format&q=60) + +## 4. Short hand with AND + +Lets consider a situation where we have a boolean value and a function. + +```js +let isPrime = true; +const startWatching = () => { + console.log('Started Watching!'); +} +``` + +This is too much code to check for the boolean condition and invoke the function, + +```js +if (isPrime) { + startWatching(); +} +``` + +How about using the short-hand using the AND(&&) operator? Yes, avoid `if` statement altogether. Cool, right? + +```js +isPrime && startWatching(); +``` + +## 5. Default value with OR + +If you ever like to set a default value for a variable, you can do it using the OR(||) operator easily. + +```js +let person = {name: 'Jack'}; +let age = person.age || 35; // sets the value 35 if age is undefined +console.log(`Age of ${person.name} is ${age}`); +``` + +## 6. Randoms + +Generating random number or getting a random item from an array are very useful methods to keep handy. I have seen them appearing multiple times in many of my projects. + +Get a random item from an array, + +```js +let planets = ['Mercury ', 'Mars', 'Venus', 'Earth', 'Neptune', 'Uranus', 'Saturn', 'Jupiter']; +let randomPlanet = planets[Math.floor(Math.random() * planets.length)]; +console.log('Random Planet', randomPlanet); +``` + +Generate a random number from a range by specifying the min and max values, + +```js +let getRandom = (min, max) => { + return Math.round(Math.random() * (max - min) + min); +} +console.log('Get random', getRandom(0, 10)); +``` + +## 7. Function default params + +In JavaScript, function arguments(or params) are like local variables to that function. You may or may not pass values for those while invoking the function. If you do not pass a value for a param, it will be `undefined` and may cause some unwanted side effects. + +There is a simple way to pass a default value to the function parameters while defining them. Here is an example where we are passing the default value `Hello` to the parameter `message` of the `greetings` function. + +```js +let greetings = (name, message='Hello,') => { + return `${message} ${name}`; +} + +console.log(greetings('Jack')); +console.log(greetings('Jack', 'Hola!')); + +``` + +## 8. Required Function Params + +Expanding on the default parameter technique, we can mark a parameter as mandatory. First define a function to throw an error with an error message, + +```js +let isRequired = () => { + throw new Error('This is a mandatory parameter.'); +} +``` + +Then assign the function as the default value for the required parameters. Remember, the default values are ignored when a value is passed for a parameter at the invocation time. But, the default value is considered if the parameter value is `undefined`. + +```js +let greetings = (name=isRequired(), message='Hello,') => { + return `${message} ${name}`; +} +console.log(greetings()); +``` + +In the above code, `name` will be undefined and that will try to set the default value for it which is the `isRequired()` function. It will throw an error as, + +![8.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1595930079306/ossRyuA7X.png?auto=format&q=60) + +## 9. Comma Operator + +I was surprised when I realized, comma(,) is a separate operator and never gone noticed. I have been using it so much in code but, never realized its true existence. + +In JavaScript, the comma(,) operator is used for evaluating each of its operands from left to right and returns the value of the last operand. + +```js +let count = 1; +let ret = (count++, count); +console.log(ret); +``` + +In the above example, the value of the variable `ret` will be, 2. Similar way, the output of the following code will be logging the value 32 into the console. + +```js +let val = (12, 32); +console.log(val); +``` + +Where do we use it? Any guesses? The most common usage of the comma(,) operator is to supply multiple parameters in a for loop. + +```js +for (var i = 0, j = 50; i <= 50; i++, j--) +``` + +## 10. Merging multiple objects + +You may have a need to merge two objects together and create a better informative object to work with. You can use the spread operator `...`(yes, three dots!). + +Consider two objects, emp and job respectively, + +```js +let emp = { + 'id': 'E_01', + 'name': 'Jack', + 'age': 32, + 'addr': 'India' +}; + +let job = { + 'title': 'Software Dev', + 'location': 'Paris' +}; +``` + +Merge them using the spread operator as, + +```js +// spread operator +let merged = {...emp, ...job}; +console.log('Spread merged', merged); +``` + +There is another way to perform this merge. Using `Object.assign()`. You can do it like, + +```js +console.log('Object assign', Object.assign({}, emp, job)); +``` + +Output: + +![10.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1595930544621/2jCCxCSnz.png?auto=format&q=60) + +Note, both spread operator and the Object.assign perform a shallow merge. In a shallow merge, the properties of the first object are overwritten with the same property values of the second object. + +For deep merge, please use something like, `_merge` of [lodash](https://lodash.com/). + +## 11. Destructuring + +The technique of breaking down the array elements and object properties as variables called, `destructuring`. Let us see it with few examples, + +### Array + +Here we have an array of emojis, + +```js +let emojis = ['🔥', '⏲️', '🏆', '🍉']; +``` + +To destructure, we would use a syntax as follows, + +```js +let [fire, clock, , watermelon] = emojis; +``` + +This is same as doing, `let fire = emojis[0];` but with lots more flexibility. Have you noticed, I have just ignored the trophy emoji using an empty space in-between? So what will be the output of this? + +```js +console.log(fire, clock, watermelon); +``` + +Output: + +![11.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1595931639636/TXaeEwgGq.png?auto=format&q=60) + +Let me also introduce something called `rest` operator here. If you want to destructure an array such that, you want to assign one or more items to variables and park rest of it into another array, you can do that using `...rest` as shown below. + +```js +let [fruit, ...rest] = emojis; +console.log(rest); +``` + +Output: + +![11.a.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1595932001526/GdWuvDoP8.png?auto=format&q=60) + +### Object + +Like arrays we can also destructure objects. + +```js +let shape = { + name: 'rect', + sides: 4, + height: 300, + width: 500 +}; +``` + +Destructuring such that, we get name, sides in couple of variables and rest are in another object. + +```js +let {name, sides, ...restObj} = shape; +console.log(name, sides); +console.log(restObj); +``` + +Output: + +![11.b.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1595932176160/97vWj-QQl.png?auto=format&q=60) + +Read more about this topic [from here](https://javascript.info/destructuring-assignment). + +## 12. Swap variables + +This must be super easy now using the concept of `destructuring` we learned just now. + +```js +let fire = '🔥'; +let fruit = '🍉'; + +[fruit, fire] = [fire, fruit]; +console.log(fire, fruit); +``` + +## 13. isArray + +Another useful method for determining if an input is an Array or not. + +```js +let emojis = ['🔥', '⏲️', '🏆', '🍉']; +console.log(Array.isArray(emojis)); + +let obj = {}; +console.log(Array.isArray(obj)); +``` + +## 14. undefined vs null + +`undefined` is where a value is not defined for a variable but, the variable has been declared. + +`null` itself is an empty and non-existent value which must be assigned to a variable explicitly. + +`undefined` and `null` are not strictly equal, + +```js +undefined === null // false +``` + +Read more about this topic [from here](https://stackoverflow.com/questions/5076944/what-is-the-difference-between-null-and-undefined-in-javascript). + +## 15. Get Query Params + +`window.location` object has bunch of utility methods and properties. We can get information about protocol, host, port, domain etc from the browser urls using these properties and methods. + +One of the properties that I found very useful is, + +```js +window.location.search +``` + +The `search` property returns the query string from the location url. Here is an example url: `https://tapasadhiary.com?project=js`. The `location.search` will return, `?project=js` + +We can use another useful interface called, `URLSearchParams` along with `location.search` to get the value of the query parameters. + +```js +let project = new URLSearchParams(location.search).get('project'); +``` + +Output: `js` + +Read more about this topic [from here](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams). + +# This is not the end + +This is not the end of the list. There are many many more. I have decided to push those to the git repo as mini examples as and when I encounter them. + +[https://github.com/atapas/js-tips-tricks](https://github.com/atapas/js-tips-tricks) + +What are your favorite JavaScript tips and tricks? How about you let us know about your favorites in the comment below? + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](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)。 From d99f34883aefb0b6cda46462668db666498c1f21 Mon Sep 17 00:00:00 2001 From: lsvih Date: Tue, 4 Aug 2020 15:20:53 +0800 Subject: [PATCH 07/10] Create 8-unheard-of-browser-apis-you-should-be-aware-of.md (#7271) --- ...-of-browser-apis-you-should-be-aware-of.md | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 article/2020/8-unheard-of-browser-apis-you-should-be-aware-of.md diff --git a/article/2020/8-unheard-of-browser-apis-you-should-be-aware-of.md b/article/2020/8-unheard-of-browser-apis-you-should-be-aware-of.md new file mode 100644 index 00000000000..6b1a9010c61 --- /dev/null +++ b/article/2020/8-unheard-of-browser-apis-you-should-be-aware-of.md @@ -0,0 +1,107 @@ +> * 原文地址:[8 Unheard of Browser APIs You Should Be Aware Of](https://medium.com/better-programming/8-unheard-of-browser-apis-you-should-be-aware-of-45247e7d5f3a) +> * 原文作者:[Mahdhi Rezvi](https://medium.com/@mahdhirezvi) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/article/2020/8-unheard-of-browser-apis-you-should-be-aware-of.md](https://github.com/xitu/gold-miner/blob/master/article/2020/8-unheard-of-browser-apis-you-should-be-aware-of.md) +> * 译者: +> * 校对者: + +# 8 Unheard of Browser APIs You Should Be Aware Of + +![Photo by [Szabo Viktor](https://unsplash.com/@vmxhu?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)](https://cdn-images-1.medium.com/max/9990/0*WzOJqPzOSrcQrX5b) + +With the increase in popularity, browsers started shipping APIs for complex functionalities that sometimes can only be implemented via a native application. Fast-forward to the present: It’s indeed quite rare to find a web application that doesn’t make use of at least one browser API. + +As the field of web development continues to grow, browser vendors also try to keep up with the rapid development around them. They constantly develop newer APIs that can bring new nativelike functionalities to your web application. Furthermore, there are some APIs that people don’t know much about, even though they’re fully supported in modern browsers. + +Here are some APIs you should be aware of — as they will play a vital role in the future. + +## The Web Locks API + +This [API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API) allows you to run a web application on multiple tabs to access and coordinate resource sharing. Although it’s quite uncommon for simple everyday web applications to run on multiple tabs, there can be advanced use cases where you’d need to run multiple browser tabs of a web application and keep them synced. This API can come in handy at those instances. + +Although APIs such as SharedWorker, BroadcastChannel, localStorage, sessionStorage, postMessage, unload handler can be used to manage tab communication and synchronization, they each have their shortcomings and require workarounds, which decreases code maintainability. The Web Locks API tries to simplify this process by bringing in a more standardized solution. + +Even though it’s enabled by default in Chrome 69, it’s still not supported by major browsers such as Firefox and Safari. + +**Note:** You should know your way around concepts like **deadlocks** to avoid falling into one when using this API. + +## The Shape Detection API + +As a web developer, you’ve probably had many instances requiring the installation of external libraries to handle the detection of elements such as faces, text, and barcodes in images. This was because there was no web standard API for developers to utilize. + +The Chrome team is trying to change this by providing an experimental [Shape Detection API](https://web.dev/shape-detection/) in Chrome browsers and making it a web standard. + +Although this feature is experimental, it can be accessed locally by enabling the #enable-experimental-web-platform-features flag in `chrome://flags`. + +![Photo by [Element5 Digital](https://unsplash.com/@element5digital?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)](https://cdn-images-1.medium.com/max/9542/0*0NoSa7j_eQ1npFws) + +## The Payment Request API + +The [Payment Request API](https://developer.mozilla.org/en-US/docs/Web/API/Payment_Request_API) helps customers and sellers complete the checkout process more seamlessly. This new approach eliminates checkout forms and improves the user’s payment experience from the ground up. With support for Apple Pay and Google Pay, this API can be expected to be a major component in the e-commerce sector. + +Furthermore, as the credentials are managed in the browser, it’s easier for the customer to switch from mobile to desktop browsers and still access their card information. This API also allows for customization from the merchant’s end. You can mention the supported payment methods and supported cards and even provide shipping options based on the shipping address. + +## The Page Visibility API + +It’s quite common for you to come across a PC with around 20 odd tabs opened in the browser. I once had a friend who just closed around 100+ tabs, after fixing a bug. Browsers have even started to [implement features](https://blog.google/products/chrome/manage-tabs-with-google-chrome/) to group your tabs to make them more organized. + +With the help of the [Page Visibility API](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API), you can detect whether your web page is idle or not. In other words, you can find out whether the tab that contains your web page is being viewed by the user. + +Although this sounds straightforward, it can be very effective in increasing the user experience of a website. There are several use cases where this API can be used. + +* Download the remainder of the application bundle resources and media resources while the browser tab is inactive. This will help you use the idle time very efficiently. +* Pause videos when the user minimizes or switches to another tab. +* Pause the image slideshow/carousal when the tab is inactive. + +Although developers have used events such as `blur` and `focus` on the window in the past, they didn’t tell whether your page was actually hidden to the user. The Page Visibility API helped addresses this issue. + +This browser API is compatible with most browsers. + +![Source: [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API#Browser_compatibility)](https://cdn-images-1.medium.com/max/2000/1*I743ncklwG4-veVjsVFfSA.png) + +## The Web Share API + +The [Web Share API](https://www.w3.org/TR/web-share/) allows you to share links, text, and files to other apps installed on the device in the same way as native apps. This API can help increase user engagement with your web application. You can read this blog [post](https://web.dev/web-share/) by Joe Medley to learn more about this cool API. + +As of mid-2020, this API is only available on Safari and on Android in Chromium forks. The [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share#Browser_compatibility) has more details regarding browser compatibility. + +![The native Share interface. Screenshot by the author.](https://cdn-images-1.medium.com/max/2000/1*uhEtWw7OEueQkMPXrn6Akw.png) + +## The Web Share Target API + +Progressive web apps are changing the way we understand applications by providing an applike experience in the web form. According to the website StateOfJS, around 48.2% of users have used PWAs and 45.5% of users are aware of what PWAs are. This shows the impact of PWAs. You can read more about PWAs in my article over [here](https://medium.com/better-programming/progressive-web-apps-an-overview-c6e4328ef2d2?source=friends_link&sk=94b7cf9919c4bb86e407604dd975dadb). + +Although PWAs have many nativelike features, they lacked a way to receive files from native apps. This API allows you to receive links, text, and files from other native applications. It’s supported on Chrome 76 and above for Android only. You can read more about this API over [here](https://web.dev/web-share-target/). + +## The Push API + +The [Push API](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) allows web applications to receive messages pushed to them from a server, regardless of whether the app is in the foreground or not. It can work even when the app isn’t loaded on a browser. This enables developers to deliver asynchronous notifications to the user in a timely manner. But for this to work, the user permission should be obtained prior to the API being used. + +You can read more about the Push API in this awesome [article](https://flaviocopes.com/push-api/) by Flavio. + +## The Cookie Store API + +Working with cookies is known to be a bit slow, as it’s synchronous. But the [Cookie Store API](https://developers.google.com/web/updates/2018/09/asynchronous-access-to-http-cookies) provides asynchronous access to HTTP cookies. Furthermore, this API also exposes these HTTP cookies to service workers. + +Although there are helper libraries to facilitate all of the usual cookie operations, with the Cookie Store API, it’ll be much easier and more performant. This API is also sometimes referred to as the Async Cookies API. + +You can read more about this API over [here](https://wicg.github.io/cookie-store/explainer.html). + +## Conclusion + +I was surprised by how cool the above APIs were when I played around with them. The only letdown of the above APIs, as mentioned before, is the lack of support from major browsers. This means it’s not simple to use these in production. But it can be assured that these APIs will definitely play a vital role in the future of browsers and web development. + +Thank you for reading, and happy coding. + +## References + +* [MDN web docs](https://developer.mozilla.org/en-US/) +* [SitePen](https://www.sitepen.com/blog/cross-tab-synchronization-with-the-web-locks-api/) +* [StateOFJS](https://2019.stateofjs.com/) +* [Creative Bloq](https://www.creativebloq.com/features/15-web-apis-youve-never-heard-of) + +> 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](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)。 From 5993addc25d6919d34addffbfacb505758018780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B5=B7=E4=B8=9C?= <41251046+lhd951220@users.noreply.github.com> Date: Fri, 7 Aug 2020 10:17:30 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E5=B8=88=E7=9A=84?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E6=80=A7=E8=83=BD=E8=89=BA=E6=9C=AF=20(#7202?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 工程师的系统性能艺术 翻译完成 * 修改完成 * 写给工程师的《系统性能兵法》 --- ...art-of-system-performance-for-engineers.md | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/TODO1/the-art-of-system-performance-for-engineers.md b/TODO1/the-art-of-system-performance-for-engineers.md index d1b759ab629..446ef07a436 100644 --- a/TODO1/the-art-of-system-performance-for-engineers.md +++ b/TODO1/the-art-of-system-performance-for-engineers.md @@ -2,82 +2,82 @@ > * 原文作者:[The AI LAB](https://medium.com/@ailab) > * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) > * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO1/the-art-of-system-performance-for-engineers.md](https://github.com/xitu/gold-miner/blob/master/TODO1/the-art-of-system-performance-for-engineers.md) -> * 译者: -> * 校对者: +> * 译者:[lhd951220](https://github.com/lhd951220) +> * 校对者:[z0gSh1u](https://github.com/z0gSh1u) [plusmultiply0](https://github.com/plusmultiply0) [kylinholmes](https://github.com/kylinholmes) -# The Art of System Performance for Engineers +# 写给工程师的《系统性能兵法》 -Encountering system performance problems are inevitable aspects of a software engineer’s life. Examples of common performance problems are: +遇到系统性能问题是软件工程师的职业生涯中不可避免的一部分。一些常见的系统性能问题示例如下: -* Disk I/O: like loading the application code from disk, or loading resources -* Network I/O: loading data from the server, or images -* Inter-Process Calls: for doing Single-Sign-On, or interacting with the OS +* 磁盘 I/O:比如,从磁盘中加载应用程序代码、加载资源 +* 网络 I/O:从服务器中加载数据或者图片 +* 进程间的调用:实现单点登录,或者与操作系统交互 -In Java, most of these problems are not immediately obvious when writing code. Class loads are implicit and most disk IO are abstracted away and can look like normal method calls. In fact, most of the time all of these (except network IO) perform fast enough to not be noticeable during casual local testing. Code loads fast because it is still in memory from a previous run, and the process you are calling into is still alive from the previous call. Even for network IO, some issues might slip out to production if the internal network is too fast to notice the problems. +在 Java 中,这些问题中的大部分都不会在写代码的时候立即暴露出来。 大多数磁盘IO方法的抽象化加之类的隐式加载,让它看起来像是在调用普通方法。实际上,大多数情况下, 所有这些问题(除了网络 IO)在非正式的本地测试中都运行得足够快, 从而不会注意到这些问题的发生。代码加载快的原因是在运行之前代码就已经在内存中,并且你调用的进程在之前的调用中仍然存活。而对于网络 IO 来说,如果因为网络太快而没有发现的问题也在会在生产环境中显现出来。 -Wouldn’t it be great if engineers could ‘see’ their calls becoming slower? +如果工程师可以“看见”他们的调用变慢,这样不是很好吗? -## Threads +## 线程 -On average, apps need to maintain a frame rate of 60fps or 16ms per frame. To achieve this the UI thread must update the UI in that time frame. Network IO usually takes multiples of 16ms, which gives us 2 options: chunk the work in smaller time slices, have something else perform the work and return the result. In Java, the second option is implemented using threads. +一般情况下,应用需要保持 60fps 的帧率,或者每 16ms 一帧。为了实现这个目的,UI 线程必须在 0 ~ 16ms 的时间范围内更新 UI。网络 IO 通常会花费数倍于 16ms 的时间,而这就给了我们两个选择:在更小的时间片中拆分任务,或者让其他进程执行工作并返回结果。在 Java 中,第二种选择是使用线程来实现的。 -Having a background thread doing the time consuming work, the results need to be show in the UI. The data still needs to be handed from the background thread to the UI thread. In practice, there might be a few patterns: +有一个后台线程来完成耗时的任务,任务的结果需要在 UI 中展示。因此需要将数据从后台线程传送到 UI 线程。实践中,可能有几种模式: -* the UI thread subscribes to a data event -* the UI thread reads/polls the data from a shared object. +* UI 线程订阅了数据的事件。 +* UI 线程从共享对象中读取/轮询数据。 -The first pattern is more robust, yet requires more work. The second is more natural, yet is riskier. As an example, throughout an app, developers might need to know the current user. Yet, when the application is launched this information still needs to be loaded from disk. This creates an interesting situation where developers block the UI thread to wait for a result generated by a background thread. Unfortunately this pattern, where developers make the UI thread wait for a background result, is more common than expected. +第一种模式更强大,但需要做更多的工作。第二种模式更加自然,但风险更大。举个例子,在整个应用程序中,开发者也许需要知道当前用户的信息。但是,当启动应用程序时,这些信息仍然需要从磁盘中加载。这导致了一个有趣的情况,开发者为了等待从后台线程生成的结果,而阻塞 UI 线程。不幸的是,这种使 UI 线程等待后台线程的模式,比预期的更为普遍。 -Wouldn’t it be great if engineers could ‘see’ when they call into a blocking method? +如果工程师可以“看见”他们调用阻塞方法,这样不是很好吗? -## Trade-offs +## 权衡取舍 -Doing all work on the UI thread is the simplest solution, and it works a lot of the time. Yet, in real world some of the work has serious performance outliers. So over time, developers would move most of that work onto a background thread. +在 UI 线程上完成所有工作是最简单解决方法,而且很多时候都有效。但是,在真实世界中,一些任务存在严重的性能问题。在大多数时候,开发者会将大部分工作转移到后台线程来完成。 -Reading the data directly from the result prepared by the background thread, is usually fine, except when there is a concurrent write, while the data is being read. So, there is the guarantee of exclusive data access through synchronization. +直接从后台线程准备好的结果中读取数据通常是更优的,除了在数据读取操作的过程中存在着并发写的操作的情况。所以,要通过同步操作来保证独占数据访问。 -Reading the data directly from the result with exclusive access, is usually quick, except sometimes when there is contention to get exclusive access to the data. Therefore, over time, developers would move some of that work to request-response models, where the UI thread subscribes to the result. +使用独占访问方式直接从结果中读取数据通常是很快的,除了有时会出现争抢对数据的独占访问的情况。因此,大多数的时候,开发者会将一些任务工作在请求-响应模式下,让 UI 线程订阅结果。 -Subscribing to data changes also requires up-front planning which might add more complexity to do the right thing for the users. +订阅数据的变化还要求预期计划,这会给用户在做正确的事情时添加更多的复杂度。 -Wouldn’t it be great if engineers could provide a simpler pattern to achieve their objectives of correct, performant code. +如果工程师可以提供一个更简单的模式来实现正确,高效的代码,这样不是更好吗? -Although these challenges might sound like difficult to tackle, here are some opportunities for engineers in terms of overcoming them: +尽管这些挑战听起来好像很难应对,这里有工程师克服这些挑战的一些建议: -1. Provide engineers with relevant run time information, to help them make better decisions while coding: e.g. create a plugin to display data. -2. Prototype a reactive/redux style user interface that is performant and easy to reason about. +1. 向工程师提供相关的运行时信息,以此帮助他们在写代码时做出更好的策略,例如:创建一个展示数据的插件。 +2. 原型是一个性能优秀且可读性好的 reactive/redux 样式的 UI。 -Needless to say, regardless of the level of hard work done by engineers, these system challenges also require some ‘soft’ skills for engineers in addition to their ‘hardware’ background. +毫无疑问,不论工程师们的努力程度如何,这些系统挑战要求工程师除了具有“硬”背景之外,还需要一些”软”技能。 -As an individual contributor, it is the engineer’s own responsibility to bring their questions and concerns to a meeting on system review. One might want to consider creating a document that is shared with a manager, put in topics one would like to discuss, and track commitments. +作为一名个人贡献者,将他们的问题和疑虑带到系统审查会议上是工程师自己的责任。可能需要创建一个与管理员共享的文档,提出一个你想要讨论的主题,并跟进提交。 -Don’t wait for company-wide official questionnaires or an all-hands meeting to raise a complex topic. Make sure you’re discussing hard things with your manager. This is all about mutual trust. +不要在公司范围的正式调查表上或在全体会议上提出一个复杂的话题。请确保你正在与经理讨论困难的事情。这些都是相互之间的信任。 -## Ship the product, and let everyone know about it +## 输出产品,并让每个人都知道它 -Success of the individual engineer is measured in different ways in various companies. Yet, one thing is relevant for everyone who wants to be recognized and rewarded. This **is shipping the product [or corresponding system development work — for infrastructure teams]**. This should be reflected in all self-reviews and discussions: what was delivered. +在不同的公司中,以不同的方式来衡量每一个工程师的成功。但是,有一个事情是与每一个想要被认可和被奖励的人相关的。就是**输出产品**,或者为基础架构团队进行相关的系统开发工作。这应该出现在自我审视和讨论中:交付了什么东西。 -You will do a huge favor to your manager if you explain which challenges you faced, and how you overcame them. Maybe your manager needs some justification to nominate you for a promotion. Help them make a strong case! +如果你向你的经理解释你面对的挑战,以及如何克服它们的,这将会对他大有帮助。也许你的经理需要一些理由来帮助你提名晋升,帮助他们找到一些理由。 -## Operate at the next level +## 进入下一等级 -Never underestimate the value of calibration sessions and don’t forget to find top-performers in your organization. There might be Senior Engineers already in other teams who might have been nominated for the promotion at exactly that calibration! +不要低估职级评定环节的价值,并且不要忘记寻找你的组织中的最佳表现者。其他团队中可能已经存在高级工程师,他们可能正是因为该标准而被提名晋升。 -If you’d like to get promoted — start operating at the next level, take the responsibility and larger project scope. Don’t wait until someone will ask you to take things over. Just go ahead, and establish your responsibility for it! The recognition will follow. +如果你想要得到晋升 —— 开始以下一等级的标准来工作,承担责任并且扩大项目范围。不能等到有人来告诉你做什么事情。现在就开始前进,建立你的责任感,对你的认可将会随之而来。 -## What it means to perform at the next level? +## 在下一等级意味着什么? ![](https://cdn-images-1.medium.com/max/2000/0*tTqppxhq9ubffWf-) -Most tech companies have levels of seniority (they might call professionals ‘Junior’, ‘Middle’, ‘Senior’, ‘Staff’, or have some corresponding codes like IC3-IC6). Some companies have career expectations defined: what you should be doing on each level of seniority. Something very rare to see is the “delivery time” dimension on career expectations. +大多数的科技公司都会对能力做等级划分(他们也许会称专业人士为“初级”,“中级”,“高级”,“专家”,或者有一些相应的编码,比如 IC3-IC6)。一些公司定义了职业期望:在每一个能力等级上你应该做什么。很少见的职业期望是“交付时间”维度。 -For example, if a Senior Engineer can deliver Feature X in two weeks, it might be okay for a mid-level Engineer to deliver it in three weeks. If organizations want to nominate the mid-level Engineer for the promotion — they should not only deliver quality work, but do it in the timeframe of the next level. +比如,如果一个高级工程师可以在两周的时间内交付特性 X,而这对于中级工程师来说可能需要三周的时间。如果组织想要提名中级工程师晋升 —— 他们不仅应该交付高质量的工作,还需要在下一等级的时间范围内完成工作。 -## Perform in a sustainable way +## 以可持续的方式发展 -Most companies have two types of compensation to recognize outstanding results of their employees. First one is a bonus, and the second one is promotion. Bonus is something people are paid for the extra mile they did during the past period. Promotion is a recognition of their competence and persistent performance at the next level. These two rewards don’t always come together. A good manager should spot if someone works 24x7, and consider flagging it as not operating at the next level yet. +大多数的公司都有两种类型的奖励来表彰员工的出色业绩。第一个是奖金,第二个则是晋升。奖金是对人们过去的一段时间的表现的额外回报。晋升是对他们拥有下一级别能力和长久以来的表现的认可。这两种奖励通常不会同时到来。一个好的管理者应该及时发现是否有人 24×7 小时制的工作,并且应该考虑将其标记为不能在下一等级中工作。 -Nobody can work 24x7 all the time, so the performance is likely to go down in the future. Make sure you exceed expectations while spending reasonable hours at work. +没有人可以在 24×7 全天候的工作,所以你的表现将会在未来下降。在花费了合理的时间在工作上的时候,确保收获是超出预期的。 > 如果发现译文存在错误或其他需要改进的地方,欢迎到 [掘金翻译计划](https://github.com/xitu/gold-miner) 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 **本文永久链接** 即为本文在 GitHub 上的 MarkDown 链接。 From d45f6eaf75a030530120057ed2fd9861b4f9e216 Mon Sep 17 00:00:00 2001 From: lsvih Date: Fri, 7 Aug 2020 11:06:48 +0800 Subject: [PATCH 09/10] Create how-to-build-redux.md (#7253) * Create how-to-build-redux.md * Update how-to-build-redux.md * Update how-to-build-redux.md * Update how-to-build-redux.md * Update how-to-build-redux.md --- article/2020/how-to-build-redux.md | 1457 ++++++++++++++++++++++++++++ 1 file changed, 1457 insertions(+) create mode 100644 article/2020/how-to-build-redux.md diff --git a/article/2020/how-to-build-redux.md b/article/2020/how-to-build-redux.md new file mode 100644 index 00000000000..2880f1a8ebf --- /dev/null +++ b/article/2020/how-to-build-redux.md @@ -0,0 +1,1457 @@ +> * 原文地址:[Build Yourself a Redux](https://zapier.com/engineering/how-to-build-redux/) +> * 原文作者:[Justin Deal](https://github.com/jdeal) +> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner) +> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/article/2020/how-to-build-redux.md](https://github.com/xitu/gold-miner/blob/master/article/2020/how-to-build-redux.md) +> * 译者: +> * 校对者: + +# Build Yourself a Redux + +Redux is a simple library that helps you manage the state of your JavaScript app. Despite that simplicity, it's easy to fall down rabbit holes when learning it. I often find myself explaining Redux, and almost always start by showing how I'd implement it. So that's what we'll do here: Start from scratch and build a working Redux implementation. Our implementation won't cover every nuance, but we'll remove most of the mystery. + +Note that technically we'll be building [Redux](https://github.com/reactjs/redux?utm_source=zapier.com&utm_medium=referral&utm_campaign=zapier) **and** [React Redux](https://github.com/reactjs/react-redux?utm_source=zapier.com&utm_medium=referral&utm_campaign=zapier). At Zapier, we pair Redux with the (awesome) UI library [React](https://facebook.github.io/react/), and that's the pairing that shows up most in the wild. But even if you use Redux with something else, most everything here will still apply. + +Let's get started! + +## Bring Your Own State Object + +Most useful apps will get their state from a server, but let's start by creating our state locally. Even if we are retrieving from the server, we have to seed the app with something anyway. Our app will be a simple note-taking app. This is mostly to avoid making yet another todo app, but it will also force us to make an interesting state decision later. + +```js +const initialState = { + nextNoteId: 1, + notes: {} +}; +``` + +So first of all, notice our data is just a plain JS object. Redux helps **manage changes to your state**, but it doesn't really care much about the state itself. + +## Why Redux? + +Before we dig any deeper, let's see what it's like to build our app without Redux. Let's just go ahead and attach our `initialState` object to `window` like this: + +```js +window.state = initialState; +``` + +Boom, there's our store! We don't need no stinking Redux. Let's make a component that adds new notes. + +```jsx +const onAddNote = () => { + const id = window.state.nextNoteId; + window.state.notes[id] = { + id, + content: '' + }; + window.state.nextNoteId++; + renderApp(); +}; + +const NoteApp = ({notes}) => ( +
+
    + { + Object.keys(notes).map(id => ( + // Obviously we should render something more interesting than the id. +
  • {id}
  • + )) + } +
+ +
+); + +const renderApp = () => { + ReactDOM.render( + , + document.getElementById('root') + ); +}; + +renderApp(); +``` + +You can try out this example live with [JSFiddle](https://jsfiddle.net/justindeal/5j3can1z/102/): + +```jsx +const initialState = { + nextNoteId: 1, + notes: {} +}; + +window.state = initialState; + +const onAddNote = () => { + const id = window.state.nextNoteId; + window.state.notes[id] = { + id, + content: '' + }; + window.state.nextNoteId++; + renderApp(); +}; + +const NoteApp = ({notes}) => ( +
+
    + { + Object.keys(notes).map(id => ( + // Obviously we should render something more interesting than the id. +
  • {id}
  • + )) + } +
+ +
+); + +const renderApp = () => { + ReactDOM.render( + , + document.getElementById('root') + ); +}; + +renderApp(); +``` + +Not a very useful app, but what's there works fine. Seems like we've proved we can get by without Redux. So this blog post is done, right? + +Not just yet… + +Let's look down the road a little bit. We add a bunch of features, build a nice backend for it, start a company so we can sell subscriptions, get lots of customers, add lots of new features, make some money, grow the company… okay, we're getting a little ahead of ourselves. + +It's difficult to see in this simple example, but on our road to success, our app may grow to include hundreds of components across hundreds of files. Our app will have asynchronous actions, so we'll have code like this: + +```js +const onAddNote = () => { + window.state.onLoading = true; + renderApp(); + api.createNote() + .then((note) => { + window.state.onLoading = false; + window.state.notes[id] = note; + renderApp(); + }); +}; +``` + +And we'll have bugs like this: + +```js +const ARCHIVE_TAG_ID = 0; + +const onAddTag = (noteId, tagId) => { + window.state.onLoading = true; + // Whoops, forgetting to render here! + // For quick local server, we might not notice. + api.addTag(noteId, tagId) + .then(() => { + window.state.onLoading = false; + window.state.tagMapping[tagId] = noteId; + if (ARCHIVE_TAG_ID) { + // Whoops, some naming bugs here. Probably from a + // rogue search and replace. Won't be noticed till + // we test that archive page that nobody really uses. + window.state.archived = window.state.archive || {}; + window.state.archived[noteId] = window.state.notes[noteId]; + delete window.state.notes[noteId]; + } + renderApp(); + }); +}; +``` + +And some hacky ad-hoc state changes like this that nobody even knows what they do: + +```js +const SomeEvilComponent = () => { + +}; +``` + +Add this all up across a large codebase with many developers over a long period of time, and we have a mounting set of problems: + +1. Rendering can be kicked off from anywhere. There will probably be weird UI glitches or unresponsiveness at seemingly random times. +2. Race conditions are lurking, even in the little bit of code we see here. +3. This mess is nearly impossible to test. You have to get the **whole** app in a specific state, then poke at it with a stick, and check the state of the **whole** app to see if it's what you expect. +4. If you have a bug, you can make some educated guesses about where to look, but ultimately, every single line of your app is a potential suspect. + +That last point is by far the worst problem and the main reason to choose Redux. If you want to shrink the complexity of your app, the best thing to do (in my opinion) is to constrain how and where you can change the state of your app. Redux isn't a panacea for the other problems, but they will likely diminish because of the same constraints. + +## The Reducer + +So how does Redux provide those constraints and help you manage state? Well, you start with a simple function that takes the current state and an action and returns the new state. So for our note-taking app, if we provide an action that adds a note, we should get a new state that has our note added to it. + +```js +const CREATE_NOTE = 'CREATE_NOTE'; +const UPDATE_NOTE = 'UPDATE_NOTE'; + +const reducer = (state = initialState, action) => { + switch (action.type) { + case CREATE_NOTE: + return // some new state with new note + case UPDATE_NOTE: + return // some new state with note updated + default: + return state + } +}; +``` + +If `switch` statements make you nauseous, you don't have to write your reducer that way. I usually use an object and point a key for each type to its corresponding handler like this: + +```js +const handlers = { + [CREATE_NOTE]: (state, action) => { + return // some new state with new note + }, + [UPDATE_NOTE]: (state, action) => { + return // some new state with note updated + } +}; + +const reducer = (state = initialState, action) => { + if (handlers[action.type]) { + return handlers[action.type](state, action); + } + return state; +}; +``` + +That part isn't too important though. The reducer is your function, and you can implement it however you want. Redux really doesn't care. + +### Immutability + +What Redux **does** care about is that your reducer is a **pure** function. Meaning, you should never, ever, ever in a million years implement your reducer like this: + +```js +const reducer = (state = initialState, action) => { + switch (action.type) { + case CREATE_NOTE: { + // DO NOT MUTATE STATE LIKE THIS!!! + state.notes[state.nextNoteId] = { + id: state.nextNoteId, + content: '' + }; + state.nextNoteId++; + return state; + } + case UPDATE_NOTE: { + // DO NOT MUTATE STATE LIKE THIS!!! + state.notes[action.id].content = action.content; + return state; + } + default: + return state; + } +}; +``` + +As a practical matter, if you mutate state like that, Redux simply won't work. Because you're mutating state, the object references won't change, so the parts of your app simply won't update correctly. It'll also make it impossible to use some Redux developer tools, because those tools keep track of previous states. If you're constantly mutating state, there's no way to go back to those previous states. + +As a matter of principal, mutating state makes it harder to build your reducer (and potentially other parts of your app) from composable parts. Pure functions are predictable, because they produce the same output when given the same input. If you make a habit of mutating state, all bets are off. Calling a function becomes indeterminate. You have to keep the whole tree of functions in your head at once. + +This predictability comes at a cost though, especially since JavaScript doesn't natively support immutable objects. For our examples, we'll make do with vanilla JavaScript, which will add some verbosity. Here's how we really need to write that reducer: + +```js +const reducer = (state = initialState, action) => { + switch (action.type) { + case CREATE_NOTE: { + const id = state.nextNoteId; + const newNote = { + id, + content: '' + }; + return { + ...state, + nextNoteId: id + 1, + notes: { + ...state.notes, + [id]: newNote + } + }; + } + case UPDATE_NOTE: { + const {id, content} = action; + const editedNote = { + ...state.notes[id], + content + }; + return { + ...state, + notes: { + ...state.notes, + [id]: editedNote + } + }; + } + default: + return state; + } +}; +``` + +I'm using [object spread properties](https://github.com/tc39/proposal-object-rest-spread?utm_source=zapier.com&utm_medium=referral&utm_campaign=zapier) (`...`) here which aren't technically part of ECMAScript yet, but it's a pretty safe bet that they will be. `Object.assign` can be used if you want to avoid non-standard features. The concept is the same either way: Don't change the state. Instead, create shallow copies of the state and any nested objects/arrays. For any parts of an object that don't change, we just reference the existing parts. If we take a closer look at this code: + +```js +return { + ...state, + notes: { + ...state.notes, + [id]: editedNote + } +}; +``` + +We're only changing the `notes` property, so other properties of `state` will remain exactly the same. The `...state` just says to re-use those existing properties as-is. Similarly, within `notes`, we're only changing the one note we're editing. The other notes that are part of `...state.notes` will remain untouched. This way, we can leverage [`shouldComponentUpdate`](https://reactjs.org/docs/react-component.html#shouldcomponentupdate) or [`PureComponent`](https://reactjs.org/docs/react-api.html#reactpurecomponent). If a component has an unchanged note as a prop, it can avoid re-rendering. Keeping that in mind, we also have to avoid writing our reducer like this: + +```js +const reducer = (state = initialState, action) => { + // Well, we avoid mutation, but still... DON'T DO THIS! + state = _.cloneDeep(state) + switch (action.type) { + // ... + case UPDATE_NOTE: { + // Hey, now I can do good old mutations. + state.notes[action.id].content = action.content; + return state; + } + default: + return state; + } +}; +``` + +That gives back your terse mutation code, and Redux will **technically** work if you do that, but you'll knee-cap all potential optimizations. Every single object and array will be brand-new for every state change, so any components depending on those objects and arrays will have to re-render, even if you didn't actually do any mutations. + +Our immutable reducer definitely requires more typing and a little more cognitive effort. But over time, you'll tend to appreciate that your state-changing functions are isolated and easy to test. For a real app, you **might** want to look at something like [lodash-fp](https://github.com/lodash/lodash/wiki/FP-Guide?utm_source=zapier.com&utm_medium=referral&utm_campaign=zapier), or [Ramda](http://ramdajs.com/) or [Immutable.js](https://facebook.github.io/immutable-js/). At Zapier, we use a variant of [immutability-helper](https://github.com/kolodny/immutability-helper?utm_source=zapier.com&utm_medium=referral&utm_campaign=zapier) which is pretty simple. I'll warn you that this is a pretty big rabbit hole though. I even started writing a [library with a different spin](https://github.com/jdeal/qim?utm_source=zapier.com&utm_medium=referral&utm_campaign=zapier). Vanilla JS is fine too and will likely play better with strong typing solutions like [Flow](https://flow.org/) and [TypeScript](https://www.typescriptlang.org/). Just make sure to stick with smaller functions. It's much like the tradeoff you make with React: you might end up with more code than the equivalent jQuery solution, but each component is far more predictable. + +### Using our Reducer + +Let's plug an action into our reducer and get out a new state. + +```js +const state0 = reducer(undefined, { + type: CREATE_NOTE +}); +``` + +Now `state0` looks like this: + +```js +{ + nextNoteId: 2, + notes: { + 1: { + id: 1, + content: '' + } + } +} +``` + +Notice we fed `undefined` in as the state in this case. Redux always passes in `undefined` as the initial state, and typically you use a default parameter like `state = initialState` to pick up your initial state object. The next time through, Redux will feed in the previous state. + +```js +const state1 = reducer(state0, { + type: UPDATE_NOTE, + id: 1, + content: 'Hello, world!' +}); +``` + +Now `state1` looks like this: + +```json +{ + nextNoteId: 2, + notes: { + 1: { + id: 1, + content: 'Hello, world!' + } + } +} +``` + +You can play with our reducer [here (JSFiddle)](https://jsfiddle.net/justindeal/kLkjt4y3/37/): + +```jsx +const CREATE_NOTE = 'CREATE_NOTE'; +const UPDATE_NOTE = 'UPDATE_NOTE'; + +const initialState = { + nextNoteId: 1, + notes: {} +}; + +const reducer = (state = initialState, action) => { + switch (action.type) { + case CREATE_NOTE: { + const id = state.nextNoteId; + const newNote = { + id, + content: '' + }; + return { + ...state, + nextNoteId: id + 1, + notes: { + ...state.notes, + [id]: newNote + } + }; + } + case UPDATE_NOTE: { + const {id, content} = action; + const editedNote = { + ...state.notes[id], + content + }; + return { + ...state, + notes: { + ...state.notes, + [id]: editedNote + } + }; + } + default: + return state; + } +}; + +const state0 = reducer(undefined, { + type: CREATE_NOTE +}); + +const state1 = reducer(state0, { + type: UPDATE_NOTE, + id: 1, + content: 'Hello, world!' +}); + +ReactDOM.render( +
{JSON.stringify(state1, null, 2)}
, + document.getElementById('root') +); +``` + +Of course, Redux doesn't keep making more variables like this, but we'll get to a real implementation soon enough. The point is that the core of Redux is really just a piece of code that you write, a simple function that takes the previous state and an action and returns the next state. Why is that function called a reducer? Because it would plug right into a standard [`reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) function. + +```js +const actions = [ + {type: CREATE_NOTE}, + {type: UPDATE_NOTE, id: 1, content: 'Hello, world!'} +]; + +const state = actions.reduce(reducer, undefined); +``` + +After this, `state` would look identical to our previous `state1`: + +```json +{ + nextNoteId: 2, + notes: { + 1: { + id: 1, + content: 'Hello, world!' + } + } +} +``` + +Play around with adding actions to our `actions` array and feeding them into the reducer [(JSFiddle link)](https://jsfiddle.net/justindeal/edogdh33/13/): + +```jsx +const CREATE_NOTE = 'CREATE_NOTE'; +const UPDATE_NOTE = 'UPDATE_NOTE'; + +const initialState = { + nextNoteId: 1, + notes: {} +}; + +const reducer = (state = initialState, action) => { + switch (action.type) { + case CREATE_NOTE: { + const id = state.nextNoteId; + const newNote = { + id, + content: '' + }; + return { + ...state, + nextNoteId: id + 1, + notes: { + ...state.notes, + [id]: newNote + } + }; + } + case UPDATE_NOTE: { + const {id, content} = action; + const editedNote = { + ...state.notes[id], + content + }; + return { + ...state, + notes: { + ...state.notes, + [id]: editedNote + } + }; + } + default: + return state; + } +}; + +const actions = [ + {type: CREATE_NOTE}, + {type: UPDATE_NOTE, id: 1, content: 'Hello, world!'} +]; + +const state = actions.reduce(reducer, undefined); + +ReactDOM.render( +
{JSON.stringify(state, null, 2)}
, + document.getElementById('root') +); +``` + +Now you can understand why Redux bills itself as "a predictable state container for JavaScript apps". Feed in the same set of actions, and you'll end up in the same state. Functional programming for the win! If you hear about Redux facilitating replay, this is roughly how that works. Out of the box though, Redux doesn't hold onto a list of actions. Instead, there's a single variable that points to the state object, and we keep changing that variable to point to the next state. That is one important mutation that **is** allowed in your app, but we'll control that mutation inside a store. + +## The Store + +Let's build a store now, which will hold onto our single state variable as well as some useful methods for setting and getting the state. + +```js +const validateAction = action => { + if (!action || typeof action !== 'object' || Array.isArray(action)) { + throw new Error('Action must be an object!'); + } + if (typeof action.type === 'undefined') { + throw new Error('Action must have a type!'); + } +}; + +const createStore = (reducer) => { + let state = undefined; + return { + dispatch: (action) => { + validateAction(action) + state = reducer(state, action); + }, + getState: () => state + }; +}; +``` + +Now you can see why we use constants instead of strings. Our action validation is a little looser than Redux's, but it's close enough to enforce that we don't misspell action types. If we pass along strings, then our action will just fall through to the default case of our reducer, and nothing much will happen, and the error may go unnoticed. But if we use constants, then typos will go through as `undefined`, which will throw an error. So we'll know right away and fix it. + +Let's create a store now and use it. + +```js +// Pass in the reducer we made earlier. +const store = createStore(reducer); + +store.dispatch({ + type: CREATE_NOTE +}); + +store.getState(); +// { +// nextNoteId: 2, +// notes: { +// 1: { +// id: 1, +// content: '' +// } +// } +// } +``` + +This is fairly functional at this point. We have a store that can use any reducer we provide to manage the state. But it's still missing an important bit: A way to subscribe to changes. Without that, it's going to require some awkward imperative code. And later when we introduce asynchronous actions, it's not going to work at all. So let's go ahead and implement subscriptions. + +```js +const createStore = reducer => { + let state; + const subscribers = []; + const store = { + dispatch: action => { + validateAction(action); + state = reducer(state, action); + subscribers.forEach(handler => handler()); + }, + getState: () => state, + subscribe: handler => { + subscribers.push(handler); + return () => { + const index = subscribers.indexOf(handler); + if (index > 0) { + subscribers.splice(index, 1); + } + }; + } + }; + store.dispatch({type: '@@redux/INIT'}); + return store; +}; +``` + +A little more code, but not **too** hard to follow. The `subscribe` function takes a `handler` function and adds that to the list of `subscribers`. It also returns a function to unsubscribe. Any time we call `dispatch`, we notify all those handlers. Now it's easy to re-render every time the state changes. + +```jsx +/////////////////////////////// +// Mini Redux implementation // +/////////////////////////////// + +const validateAction = action => { + if (!action || typeof action !== 'object' || Array.isArray(action)) { + throw new Error('Action must be an object!'); + } + if (typeof action.type === 'undefined') { + throw new Error('Action must have a type!'); + } +}; + +const createStore = reducer => { + let state; + const subscribers = []; + const store = { + dispatch: action => { + validateAction(action); + state = reducer(state, action); + subscribers.forEach(handler => handler()); + }, + getState: () => state, + subscribe: handler => { + subscribers.push(handler); + return () => { + const index = subscribers.indexOf(handler); + if (index > 0) { + subscribers.splice(index, 1); + } + }; + } + }; + store.dispatch({type: '@@redux/INIT'}); + return store; +}; + +////////////////////// +// Our action types // +////////////////////// + +const CREATE_NOTE = 'CREATE_NOTE'; +const UPDATE_NOTE = 'UPDATE_NOTE'; + +///////////////// +// Our reducer // +///////////////// + +const initialState = { + nextNoteId: 1, + notes: {} +}; + +const reducer = (state = initialState, action) => { + switch (action.type) { + case CREATE_NOTE: { + const id = state.nextNoteId; + const newNote = { + id, + content: '' + }; + return { + ...state, + nextNoteId: id + 1, + notes: { + ...state.notes, + [id]: newNote + } + }; + } + case UPDATE_NOTE: { + const {id, content} = action; + const editedNote = { + ...state.notes[id], + content + }; + return { + ...state, + notes: { + ...state.notes, + [id]: editedNote + } + }; + } + default: + return state; + } +}; + +/////////////// +// Our store // +/////////////// + +const store = createStore(reducer); + +/////////////////////////////////////////////// +// Render our app whenever the store changes // +/////////////////////////////////////////////// + +store.subscribe(() => { + ReactDOM.render( +
{JSON.stringify(store.getState(), null, 2)}
, + document.getElementById('root') + ); +}); + +////////////////////// +// Dispatch actions // +////////////////////// + +store.dispatch({ + type: CREATE_NOTE +}); + +store.dispatch({ + type: UPDATE_NOTE, + id: 1, + content: 'Hello, world!' +}); +``` + +[Play with the code](https://jsfiddle.net/justindeal/8cpu4ydj/27/) and dispatch more actions. The rendered HTML will always reflect the store state. Of course, for a real app, we want to wire up those `dispatch` functions to user actions. We'll get to that soon enough! + +## Bring Your own Components + +How do you make components that work with Redux? Just make plain old React components that take props. You bring your own state, so make components that work with that state (or parts of it). There are some nuances that **might** affect your design later, particularly with respect to performance, but for the most part, boring components are a good place to start. So let's do that for our app now. + +```jsx +const NoteEditor = ({note, onChangeNote, onCloseNote}) => ( +
+
+