From aefb97eaf5fb56933c92384dcfdf19353904cbf0 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sat, 4 Aug 2018 16:46:12 -0300 Subject: [PATCH 01/16] Started translation Pt-Br --- Framework/framework-br.md | 731 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 731 insertions(+) create mode 100644 Framework/framework-br.md diff --git a/Framework/framework-br.md b/Framework/framework-br.md new file mode 100644 index 00000000..9d390374 --- /dev/null +++ b/Framework/framework-br.md @@ -0,0 +1,731 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [MVVM](#mvvm) + - [Verificação suja](#dirty-checking) + - [Sequestro de dados](#data-hijacking) + - [Proxy vs. Obeject.defineProperty](#proxy-vs-obejectdefineproperty) +- [Princípios de rota](#routing-principle) +- [Virtual Dom](#virtual-dom) + - [Por que Virtual Dom é preciso](#why-virtual-dom-is-needed) + - [Intrudução ao algoritmo do Virtual Dom](#virtual-dom-algorithm-introduction) + - [Implementação do algoritimo do Virtual Dom](#virtual-dom-algorithm-implementation) + - [recursão da árvore](#recursion-of-the-tree) + - [varificando mudança de propriedades](#checking-property-changes) + - [Implementação do algoritimo para detectar mudanças de lista](#algorithm-implementation-for-detecting-list-changes) + - [Iterando e marcando elementos filhos](#iterating-and-marking-child-elements) + - [Diferença de renderização](#rendering-difference) + - [Fim](#the-end) + + + +# MVVM + +MVVM consiste dos três seguintes conceitos + +* View: Interface +* Model:Modelo de dados +* ViewModel:Como uma ponte responsável pela comunicação entre a View e o Model + +Na época do JQuery, se você precisar atualizar a UI, você precisar obter o DOM correspondente e então atualizar a UI, então os dados e as regras de negócio estão fortemente acoplados com a página. + +No MVVM, o UI é condizudo pelos dados. Uma vez que é o dado mudou, a UI correspondente será atualizada. Se a UI mudar, o dado correspondente também ira mudar. Dessas forma, nos preocupamos apenas com o fluxo de dados no processamento do negócio sem lidar com a página diretamente. ViewModel apenas se preocupa com o processamento de dados e regras de negócio e não se preocupa como a View manipula os dados. + +In MVVM, the UI is driven by data. Once the data is changed, the corresponding UI will be refreshed. If the UI changes, the corresponding data will also be changed. In this way, we can only care about the data flow in business processing without dealing with the page directly. ViewModel only cares about the processing of data and business and does not care how View handles data. In this case, we can separate the View from the Model. If either party changes, it does not necessarily need to change the other party, and some reusable logic can be placed in a ViewModel, allowing multiple Views to reuse this ViewModel. + +In MVVM, the core is the two-way binding of data, such as dirty checking by Angular and data hijacking in Vue. + + +## Dirty Checking + +When the specified event is triggered, it will enter the dirty checking and call the `$digest` loop to walk through all the data observers to determine whether the current value is different from the previous value. If a change is detected, it will call the `$watch` function, and then call the `$digest` loop again until no changes are found. The cycle is at least two times, up to ten times. + +Although dirty checking has inefficiencies, it can complete the task without caring about how the data is changed, but the two-way binding in `Vue` is problematic. And dirty checking can achieve batch detection of updated values, and then unified update UI, greatly reducing the number of operating DOM. Therefore, inefficiency is also relative, and this is what the benevolent sees the wise and sees wisdom. + + +## Data hijacking + +Vue internally uses `Obeject.defineProperty()` to implement two-way binding, which allows you to listen for events of `set` and `get`. + +```js +var data = { name: 'yck' } +observe(data) +let name = data.name // -> get value +data.name = 'yyy' // -> change value + +function observe(obj) { + // judge the type + if (!obj || typeof obj !== 'object') { + return + } + Object.keys(obj).forEach(key => { + defineReactive(obj, key, obj[key]) + }) +} + +function defineReactive(obj, key, val) { + // recurse the properties of child + observe(val) + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter() { + console.log('get value') + return val + }, + set: function reactiveSetter(newVal) { + console.log('change value') + val = newVal + } + }) +} +``` + +The above code simply implements how to listen for the `set` and `get` events of the data, but that's not enough. You also need to add a Publish/Subscribe to the property when appropriate. + +```html +
+ {{name}} +
+``` + +::: v-pre +In the process of parsing the template code like above, when encountering `{{name}}`, add a publish/subscribe to the property `name` +::: + +```js +// decouple by Dep +class Dep { + constructor() { + this.subs = [] + } + addSub(sub) { + // Sub is an instance of Watcher + this.subs.push(sub) + } + notify() { + this.subs.forEach(sub => { + sub.update() + }) + } +} +// Global property, configure Watcher with this property +Dep.target = null + +function update(value) { + document.querySelector('div').innerText = value +} + +class Watcher { + constructor(obj, key, cb) { + // Point Dep.target to itself + // Then trigger the getter of the property to add the listener + // Finally, set Dep.target as null + Dep.target = this + this.cb = cb + this.obj = obj + this.key = key + this.value = obj[key] + Dep.target = null + } + update() { + // get the new value + this.value = this.obj[this.key] + // update Dom with the update method + this.cb(this.value) + } +} +var data = { name: 'yck' } +observe(data) +// Simulate the action triggered by parsing the `{{name}}` +new Watcher(data, 'name', update) +// update Dom innerText +data.name = 'yyy' +``` + +Next, improve on the `defineReactive` function. + +```js +function defineReactive(obj, key, val) { + // recurse the properties of child + observe(val) + let dp = new Dep() + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter() { + console.log('get value') + // Add Watcher to the subscription + if (Dep.target) { + dp.addSub(Dep.target) + } + return val + }, + set: function reactiveSetter(newVal) { + console.log('change value') + val = newVal + // Execute the update method of Watcher + dp.notify() + } + }) +} +``` + +The above implements a simple two-way binding. The core idea is to manually trigger the getter of the property to add the Publish/Subscribe. + + + +## Proxy vs. Obeject.defineProperty + +Although `Obeject.defineProperty` has been able to implement two-way binding, it is still flawed. + +* It can only implement data hijacking on properties, so it needs deep traversal of the entire object +* it can't listen to changes in data for arrays + +Although Vue can detect the changes in array data, it is actually a hack and is flawed. + +```js +const arrayProto = Array.prototype +export const arrayMethods = Object.create(arrayProto) +// hack the following functions +const methodsToPatch = [ + 'push', + 'pop', + 'shift', + 'unshift', + 'splice', + 'sort', + 'reverse' +] +methodsToPatch.forEach(function (method) { + // get the native function + const original = arrayProto[method] + def(arrayMethods, method, function mutator (...args) { + // call the native function + const result = original.apply(this, args) + const ob = this.__ob__ + let inserted + switch (method) { + case 'push': + case 'unshift': + inserted = args + break + case 'splice': + inserted = args.slice(2) + break + } + if (inserted) ob.observeArray(inserted) + // trigger the update + ob.dep.notify() + return result + }) +}) +``` + +On the other hand, `Proxy` doesn't have the above problem. It natively supports listening to array changes and can intercept the entire object directly, so Vue will also replace `Obeject.defineProperty` with `Proxy` in the next big version. + +```js +let onWatch = (obj, setBind, getLogger) => { + let handler = { + get(target, property, receiver) { + getLogger(target, property) + return Reflect.get(target, property, receiver); + }, + set(target, property, value, receiver) { + setBind(value); + return Reflect.set(target, property, value); + } + }; + return new Proxy(obj, handler); +}; + +let obj = { a: 1 } +let value +let p = onWatch(obj, (v) => { + value = v +}, (target, property) => { + console.log(`Get '${property}' = ${target[property]}`); +}) +p.a = 2 // bind `value` to `2` +p.a // -> Get 'a' = 2 +``` + +# Routing principle + +The front-end routing is actually very simple to implement. The essence is to listen to changes in the URL, then match the routing rules, display the corresponding page, and no need to refresh. Currently, there are only two implementations of the route used by a single page. + +- hash mode +- history mode + + +`www.test.com/#/` is the hash URL. When the hash value after `#` changes, no request will be sent to server. You can listen to the URL change through the `hashchange` event, and then jump to the corresponding page. + +![](https://user-gold-cdn.xitu.io/2018/7/13/1649266be7ec2fb7?w=981&h=546&f=png&s=36646) + +History mode is a new feature of HTML5, which is more beautiful than Hash URL. + +![](https://user-gold-cdn.xitu.io/2018/7/13/164926dc15df79d3?w=1244&h=546&f=png&s=49647) + +# Virtual Dom + +[source code](https://github.com/KieSun/My-wheels/tree/master/Virtual%20Dom) + +## Why Virtual Dom is needed + +As we know, modifying DOM is a costly task. We could consider using JS objects to simulate DOM objects, since operating on JS objects is much more time saving than operating on DOM. + +For example + +```js +// Let's assume this array simulates a ul which contains five li's. +[1, 2, 3, 4, 5] +// using this to replace the ul above. +[1, 2, 5, 4] +``` + +From the above example, it's apparent that the first ul's 3rd li is removed, and the 4th and the 5th are exchanged positions. + +If the previous operation is applied to DOM, we have the following code: + +```js +// removing the 3rd li +ul.childNodes[2].remove() +// interexchanging positions between the 4th and the 5th +let fromNode = ul.childNodes[4] +let toNode = node.childNodes[3] +let cloneFromNode = fromNode.cloneNode(true) +let cloenToNode = toNode.cloneNode(true) +ul.replaceChild(cloneFromNode, toNode) +ul.replaceChild(cloenToNode, fromNode) +``` + +Of course, in actual operations, we need an indentifier for each node, as an index for checking if two nodes are identical. This is why both Vue and React's official documentation suggests using a unique identifier `key` for nodes in a list to ensure efficiency. + +DOM element can not only be simulated, but they can also be rendered by JS objects. + +Below is a simple implementation of a JS object simulating a DOM element. + +```js +export default class Element { + /** + * @param {String} tag 'div' + * @param {Object} props { class: 'item' } + * @param {Array} children [ Element1, 'text'] + * @param {String} key option + */ + constructor(tag, props, children, key) { + this.tag = tag + this.props = props + if (Array.isArray(children)) { + this.children = children + } else if (isString(children)) { + this.key = children + this.children = null + } + if (key) this.key = key + } + // render + render() { + let root = this._createElement( + this.tag, + this.props, + this.children, + this.key + ) + document.body.appendChild(root) + return root + } + create() { + return this._createElement(this.tag, this.props, this.children, this.key) + } + // create an element + _createElement(tag, props, child, key) { + // create an element with tag + let el = document.createElement(tag) + // set properties on the element + for (const key in props) { + if (props.hasOwnProperty(key)) { + const value = props[key] + el.setAttribute(key, value) + } + } + if (key) { + el.setAttribute('key', key) + } + // add children nodes recursively + if (child) { + child.forEach(element => { + let child + if (element instanceof Element) { + child = this._createElement( + element.tag, + element.props, + element.children, + element.key + ) + } else { + child = document.createTextNode(element) + } + el.appendChild(child) + }) + } + return el + } +} +``` + +## Virtual Dom algorithm introduction + +The next step after using JS to implement DOM element is to detect object changes. + +DOM is a multi-branching tree. If we were to compare the old and the new trees thoroughly, the time complexity would be O(n ^ 3), which is simply unacceptable. Therefore, the React team optimized their algorithm to achieve an O(n) complexity for detecting changes. + +The key to achieving O(n) is to only compare the nodes on the same level rather than across levels. This works because in actual usage we rarely move DOM elements across levels. + +We then have two steps of the algorithm. + +- from top to bottom, from left to right to iterate the object, aka depth first search. This step adds an index to every node, for rendering the differences later. +- whenever a node has a child element, we check whether the child element changed. + +## Virtual Dom algorithm implementation + +### recursion of the tree + +First let's implement the recursion algorithm of the tree. Before doing that, let's consider the different cases of comparing two nodes. + +1. new nodes's `tagName` or `key` is different from that of the old one. This menas the old node is replaced, and we don't have to recurse on the node any more because the whole subtree is removed. +2. new node's `tagName` and `key` (maybe nonexistent) are the same as the old's. We start recursing on the subtree. +3. no new node appears. No operation needed. + +```js +import { StateEnums, isString, move } from './util' +import Element from './element' + +export default function diff(oldDomTree, newDomTree) { + // for recording changes + let pathchs = {} + // the index starts at 0 + dfs(oldDomTree, newDomTree, 0, pathchs) + return pathchs +} + +function dfs(oldNode, newNode, index, patches) { + // for saving the subtree changes + let curPatches = [] + // three cases + // 1. no new node, do nothing + // 2. new nodes' tagName and `key` are different from the old one's, replace + // 3. new nodes' tagName and key are the same as the old one's, start recursing + if (!newNode) { + } else if (newNode.tag === oldNode.tag && newNode.key === oldNode.key) { + // check whether properties changed + let props = diffProps(oldNode.props, newNode.props) + if (props.length) curPatches.push({ type: StateEnums.ChangeProps, props }) + // recurse the subtree + diffChildren(oldNode.children, newNode.children, index, patches) + } else { + // different node, replace + curPatches.push({ type: StateEnums.Replace, node: newNode }) + } + + if (curPatches.length) { + if (patches[index]) { + patches[index] = patches[index].concat(curPatches) + } else { + patches[index] = curPatches + } + } +} +``` + +### checking property changes + +We also have three steps for checking for property changes + +1. iterate the old property list, check if the property still exists in the new property list. +2. iterate the new property list, check if there are changes for properties existing in both lists. +3. for the second step, also check if a property doesn't exist in the old property list. + +```js +function diffProps(oldProps, newProps) { + // three steps for checking for props + // iterate oldProps for removed properties + // iterate newProps for chagned property values + // lastly check if new properties are added + let change = [] + for (const key in oldProps) { + if (oldProps.hasOwnProperty(key) && !newProps[key]) { + change.push({ + prop: key + }) + } + } + for (const key in newProps) { + if (newProps.hasOwnProperty(key)) { + const prop = newProps[key] + if (oldProps[key] && oldProps[key] !== newProps[key]) { + change.push({ + prop: key, + value: newProps[key] + }) + } else if (!oldProps[key]) { + change.push({ + prop: key, + value: newProps[key] + }) + } + } + } + return change +} +``` + +### Algorithm Implementation for Detecting List Changes + +This algorithm is the core of the Virtual Dom. Let's go down the list. +The main steps are similar to checking property changes. There are also three steps. + +1. iterate the old node list, check if the node still exists in the new list. +2. iterate the new node list, check if there is any new node. +3. for the second step, also check if a node moved. + +PS: this algorithm only handles nodes with `key`s. + +```js +function listDiff(oldList, newList, index, patches) { + // to make the iteration more convenient, first take all keys from both lists + let oldKeys = getKeys(oldList) + let newKeys = getKeys(newList) + let changes = [] + + // for saving the node daa after changes + // there are several advantages of using this array to save + // 1. we can correctly obtain the index of the deleted node + // 2. we only need to operate on the DOM once for interexchanged nodes + // 3. we only need to iterate for the checking in the `diffChildren` function + // we don't need to check again for nodes existing in both lists + let list = [] + oldList && + oldList.forEach(item => { + let key = item.key + if (isString(item)) { + key = item + } + // checking if the new children has the current node + // if not then delete + let index = newKeys.indexOf(key) + if (index === -1) { + list.push(null) + } else list.push(key) + }) + // array after iterative changes + let length = list.length + // since deleting array elements changes the indices + // we remove from the back to make sure indices stay the same + for (let i = length - 1; i >= 0; i--) { + // check if the current element is null, if so then it means we need to remove it + if (!list[i]) { + list.splice(i, 1) + changes.push({ + type: StateEnums.Remove, + index: i + }) + } + } + // iterate the new list, check if a node is added or moved + // also add and move nodes for `list` + newList && + newList.forEach((item, i) => { + let key = item.key + if (isString(item)) { + key = item + } + // check if the old children has the current node + let index = list.indexOf(key) + // if not then we need to insert + if (index === -1 || key == null) { + changes.push({ + type: StateEnums.Insert, + node: item, + index: i + }) + list.splice(i, 0, key) + } else { + // found the node, need to check if it needs to be moved. + if (index !== i) { + changes.push({ + type: StateEnums.Move, + from: index, + to: i + }) + move(list, index, i) + } + } + }) + return { changes, list } +} + +function getKeys(list) { + let keys = [] + let text + list && + list.forEach(item => { + let key + if (isString(item)) { + key = [item] + } else if (item instanceof Element) { + key = item.key + } + keys.push(key) + }) + return keys +} +``` + +### Iterating and Marking Child Elements + +For this function, there are two main functionalities. + +1. checking differences between two lists +2. marking nodes + +In general, the functionalities impelemented are simple. + +```js +function diffChildren(oldChild, newChild, index, patches) { + let { changes, list } = listDiff(oldChild, newChild, index, patches) + if (changes.length) { + if (patches[index]) { + patches[index] = patches[index].concat(changes) + } else { + patches[index] = changes + } + } + // marking last iterated node + let last = null + oldChild && + oldChild.forEach((item, i) => { + let child = item && item.children + if (child) { + index = + last && last.children ? index + last.children.length + 1 : index + 1 + let keyIndex = list.indexOf(item.key) + let node = newChild[keyIndex] + // only iterate nodes existing in both lists + // no need to visit the added or removed ones + if (node) { + dfs(item, node, index, patches) + } + } else index += 1 + last = item + }) +} +``` + +### Rendering Difference + +From the earlier algorithms, we can already get the differences between two trees. After knowing the differences, we need to locally update DOM. Let's take a look at the last step of Virtual Dom algorithms. + +Two main functionalities for this function + +1. Deep search the tree and extract the nodes needing modifications +2. Locally update DOM + +This code snippet is pretty easy to understand as a whole. + +```js +let index = 0 +export default function patch(node, patchs) { + let changes = patchs[index] + let childNodes = node && node.childNodes + // this deep search is the same as the one in diff algorithm + if (!childNodes) index += 1 + if (changes && changes.length && patchs[index]) { + changeDom(node, changes) + } + let last = null + if (childNodes && childNodes.length) { + childNodes.forEach((item, i) => { + index = + last && last.children ? index + last.children.length + 1 : index + 1 + patch(item, patchs) + last = item + }) + } +} + +function changeDom(node, changes, noChild) { + changes && + changes.forEach(change => { + let { type } = change + switch (type) { + case StateEnums.ChangeProps: + let { props } = change + props.forEach(item => { + if (item.value) { + node.setAttribute(item.prop, item.value) + } else { + node.removeAttribute(item.prop) + } + }) + break + case StateEnums.Remove: + node.childNodes[change.index].remove() + break + case StateEnums.Insert: + let dom + if (isString(change.node)) { + dom = document.createTextNode(change.node) + } else if (change.node instanceof Element) { + dom = change.node.create() + } + node.insertBefore(dom, node.childNodes[change.index]) + break + case StateEnums.Replace: + node.parentNode.replaceChild(change.node.create(), node) + break + case StateEnums.Move: + let fromNode = node.childNodes[change.from] + let toNode = node.childNodes[change.to] + let cloneFromNode = fromNode.cloneNode(true) + let cloenToNode = toNode.cloneNode(true) + node.replaceChild(cloneFromNode, toNode) + node.replaceChild(cloenToNode, fromNode) + break + default: + break + } + }) +} +``` + +## The End + +The implementation of the Virtual Dom algorithms contains the following three steps: + +1. Simulate the creation of DOM objects through JS +2. Check differences between two objects +3. Render the differences + +```js +let test4 = new Element('div', { class: 'my-div' }, ['test4']) +let test5 = new Element('ul', { class: 'my-div' }, ['test5']) + +let test1 = new Element('div', { class: 'my-div' }, [test4]) + +let test2 = new Element('div', { id: '11' }, [test5, test4]) + +let root = test1.render() + +let pathchs = diff(test1, test2) +console.log(pathchs) + +setTimeout(() => { + console.log('start updating') + patch(root, pathchs) + console.log('end updating') +}, 1000) +``` + +Although the current implementation is simple, it's definitely enough for understanding Virtual Dom algorithms. \ No newline at end of file From 0b1f1a6eb2f4e6b5f8901e394d27037703dafd2a Mon Sep 17 00:00:00 2001 From: Lucas Date: Sat, 4 Aug 2018 21:24:24 -0300 Subject: [PATCH 02/16] Finished MVVM --- Framework/framework-br.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Framework/framework-br.md b/Framework/framework-br.md index 9d390374..b4235e37 100644 --- a/Framework/framework-br.md +++ b/Framework/framework-br.md @@ -30,12 +30,9 @@ MVVM consiste dos três seguintes conceitos Na época do JQuery, se você precisar atualizar a UI, você precisar obter o DOM correspondente e então atualizar a UI, então os dados e as regras de negócio estão fortemente acoplados com a página. -No MVVM, o UI é condizudo pelos dados. Uma vez que é o dado mudou, a UI correspondente será atualizada. Se a UI mudar, o dado correspondente também ira mudar. Dessas forma, nos preocupamos apenas com o fluxo de dados no processamento do negócio sem lidar com a página diretamente. ViewModel apenas se preocupa com o processamento de dados e regras de negócio e não se preocupa como a View manipula os dados. - -In MVVM, the UI is driven by data. Once the data is changed, the corresponding UI will be refreshed. If the UI changes, the corresponding data will also be changed. In this way, we can only care about the data flow in business processing without dealing with the page directly. ViewModel only cares about the processing of data and business and does not care how View handles data. In this case, we can separate the View from the Model. If either party changes, it does not necessarily need to change the other party, and some reusable logic can be placed in a ViewModel, allowing multiple Views to reuse this ViewModel. - -In MVVM, the core is the two-way binding of data, such as dirty checking by Angular and data hijacking in Vue. +No MVVM, o UI é condizudo pelos dados. Uma vez que é o dado mudou, a UI correspondente será atualizada. Se a UI mudar, o dado correspondente também ira mudar. Dessas forma, nos preocupamos apenas com o fluxo de dados no processamento do negócio sem lidar com a página diretamente. ViewModel apenas se preocupa com o processamento de dados e regras de negócio e não se preocupa como a View manipula os dados. Nesse caso, nós podemos separar a View da Model. Se qualquer uma das partes mudarem, isso não necessariamente precisa mudar a outra parte, e qualquer lógica reusável pode ser colocado na ViewModel, permitindo multiplas View reusar esse ViewModel. +NO MVVM, o núcleo é two-way binding de dados, tal como a verificação suja do Angular e sequestro de dados no Vue. ## Dirty Checking From e5b9db086ff50be14f85b52a5c86e335b47bb4b3 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sat, 4 Aug 2018 21:41:25 -0300 Subject: [PATCH 03/16] Finished Dirty Checking --- Framework/framework-br.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Framework/framework-br.md b/Framework/framework-br.md index b4235e37..55e156ef 100644 --- a/Framework/framework-br.md +++ b/Framework/framework-br.md @@ -30,15 +30,15 @@ MVVM consiste dos três seguintes conceitos Na época do JQuery, se você precisar atualizar a UI, você precisar obter o DOM correspondente e então atualizar a UI, então os dados e as regras de negócio estão fortemente acoplados com a página. -No MVVM, o UI é condizudo pelos dados. Uma vez que é o dado mudou, a UI correspondente será atualizada. Se a UI mudar, o dado correspondente também ira mudar. Dessas forma, nos preocupamos apenas com o fluxo de dados no processamento do negócio sem lidar com a página diretamente. ViewModel apenas se preocupa com o processamento de dados e regras de negócio e não se preocupa como a View manipula os dados. Nesse caso, nós podemos separar a View da Model. Se qualquer uma das partes mudarem, isso não necessariamente precisa mudar a outra parte, e qualquer lógica reusável pode ser colocado na ViewModel, permitindo multiplas View reusar esse ViewModel. +No MVVM, o UI é condizudo pelos dados. Uma vez que o dado mudou, a UI correspondente será atualizada. Se a UI mudar, o dado correspondente também ira mudar. Dessas forma, nos preocupamos apenas com o fluxo de dados no processamento do negócio sem lidar com a página diretamente. ViewModel apenas se preocupa com o processamento de dados e regras de negócio e não se preocupa como a View manipula os dados. Nesse caso, nós podemos separar a View da Model. Se qualquer uma das partes mudarem, isso não necessariamente precisa mudar na outra parte, e qualquer lógica reusável pode ser colocado na ViewModel, permitindo multiplas View reusarem esse ViewModel. -NO MVVM, o núcleo é two-way binding de dados, tal como a verificação suja do Angular e sequestro de dados no Vue. +No MVVM, o núcleo é two-way binding de dados, tal como a verificação suja do Angular e sequestro de dados no Vue. -## Dirty Checking +## Verificação Suja -When the specified event is triggered, it will enter the dirty checking and call the `$digest` loop to walk through all the data observers to determine whether the current value is different from the previous value. If a change is detected, it will call the `$watch` function, and then call the `$digest` loop again until no changes are found. The cycle is at least two times, up to ten times. +Quando o evento especificado é disparado, ele irá entrar na verificação suja e chamar o laço `$digest` caminhando através de todos os dados observados para determinar se o valor atual é diferente do valor anterior. Se a mudança é detectada, irá chamar a função `$watch`, e então chamar o laço `$digest` novamente até que nenhuma mudança seja encontrada. O ciclo vai de pelo menos de duas vezes até dez vezes. -Although dirty checking has inefficiencies, it can complete the task without caring about how the data is changed, but the two-way binding in `Vue` is problematic. And dirty checking can achieve batch detection of updated values, and then unified update UI, greatly reducing the number of operating DOM. Therefore, inefficiency is also relative, and this is what the benevolent sees the wise and sees wisdom. +Embora a verificação suja ser ineficiente, ele consegue completar a tarefa sem se preocupar sobre como o dado mudou, mas o two-way binding no `Vue` é problemático. E a verificação suja consegue alcançar detecção de lotes de valores atualizados, e então unificar atualizações na UI, com grandeza reduzindo o número de operações no DOM. Assim sendo, ineficiência é relativa, e é assim que o benevolente vê o sábio e a sabedoria. ## Data hijacking From 6e86bc7da88731a7cae1767f1068efd0d7f31bab Mon Sep 17 00:00:00 2001 From: Lucas Date: Sat, 4 Aug 2018 21:58:27 -0300 Subject: [PATCH 04/16] Finished Data hijacking --- Framework/framework-br.md | 44 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Framework/framework-br.md b/Framework/framework-br.md index 55e156ef..37d69938 100644 --- a/Framework/framework-br.md +++ b/Framework/framework-br.md @@ -40,19 +40,18 @@ Quando o evento especificado é disparado, ele irá entrar na verificação suja Embora a verificação suja ser ineficiente, ele consegue completar a tarefa sem se preocupar sobre como o dado mudou, mas o two-way binding no `Vue` é problemático. E a verificação suja consegue alcançar detecção de lotes de valores atualizados, e então unificar atualizações na UI, com grandeza reduzindo o número de operações no DOM. Assim sendo, ineficiência é relativa, e é assim que o benevolente vê o sábio e a sabedoria. +## Sequesto de dados -## Data hijacking - -Vue internally uses `Obeject.defineProperty()` to implement two-way binding, which allows you to listen for events of `set` and `get`. +Vuew internamente usa `Obeject.defineProperty()` para implementar o two-way binding, do qual permite você escutar por eventos de `set` e `get`. ```js var data = { name: 'yck' } observe(data) -let name = data.name // -> get value -data.name = 'yyy' // -> change value +let name = data.name // -> ontém o valor +data.name = 'yyy' // -> muda o valor function observe(obj) { - // judge the type + // juiz do tipo if (!obj || typeof obj !== 'object') { return } @@ -62,7 +61,7 @@ function observe(obj) { } function defineReactive(obj, key, val) { - // recurse the properties of child + // recurse as propriedades dos filhos observe(val) Object.defineProperty(obj, key, { enumerable: true, @@ -79,7 +78,7 @@ function defineReactive(obj, key, val) { } ``` -The above code simply implements how to listen for the `set` and `get` events of the data, but that's not enough. You also need to add a Publish/Subscribe to the property when appropriate. +O código acima é uma simples implementação de como escutar os eventos `set` e `get` dos dados, mas isso não é o suficiente. Você também precisa adicionar um Publish/Subscribe para as propriedades quando apropriado. ```html
@@ -88,17 +87,17 @@ The above code simply implements how to listen for the `set` and `get` events of ``` ::: v-pre -In the process of parsing the template code like above, when encountering `{{name}}`, add a publish/subscribe to the property `name` +Nesse processo, análisando o código do modelo, como acima, quando encontrando `{{name}}`, adicione um publish/subscribe para a propriedade `name` ::: ```js -// decouple by Dep +// dissociar por Dep class Dep { constructor() { this.subs = [] } addSub(sub) { - // Sub is an instance of Watcher + // Sub é uma instância do observador this.subs.push(sub) } notify() { @@ -107,7 +106,7 @@ class Dep { }) } } -// Global property, configure Watcher with this property +// Propriedade global, configura o observador com essa propriedade Dep.target = null function update(value) { @@ -116,9 +115,9 @@ function update(value) { class Watcher { constructor(obj, key, cb) { - // Point Dep.target to itself - // Then trigger the getter of the property to add the listener - // Finally, set Dep.target as null + // Aponte Dep.target para se mesmo + // Então dispare o getter para a propriedade adicionar o ouvinte + // Finalmente, set Dep.target como null Dep.target = this this.cb = cb this.obj = obj @@ -127,17 +126,18 @@ class Watcher { Dep.target = null } update() { - // get the new value + // obtenha o novo valor this.value = this.obj[this.key] // update Dom with the update method + // atualize o DOM com o método de atualizar this.cb(this.value) } } var data = { name: 'yck' } observe(data) -// Simulate the action triggered by parsing the `{{name}}` +// Simulando a ação disparada analisando o `{{name}}` new Watcher(data, 'name', update) -// update Dom innerText +// atualiza o DOM innerText data.name = 'yyy' ``` @@ -145,7 +145,7 @@ Next, improve on the `defineReactive` function. ```js function defineReactive(obj, key, val) { - // recurse the properties of child + // recurse as propriedades do filho observe(val) let dp = new Dep() Object.defineProperty(obj, key, { @@ -153,7 +153,7 @@ function defineReactive(obj, key, val) { configurable: true, get: function reactiveGetter() { console.log('get value') - // Add Watcher to the subscription + // Adiciona o Watcher para inscrição if (Dep.target) { dp.addSub(Dep.target) } @@ -162,14 +162,14 @@ function defineReactive(obj, key, val) { set: function reactiveSetter(newVal) { console.log('change value') val = newVal - // Execute the update method of Watcher + // Execute o método de atualização do Watcher dp.notify() } }) } ``` -The above implements a simple two-way binding. The core idea is to manually trigger the getter of the property to add the Publish/Subscribe. +A implementação acima é um simples two-way binding. A idéia central é manualmente disparar o getter das propriedades para adicionar o Publish/Subscribe. From 90b1d47750b2fd5ac6c13b33e8d09304926214d4 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 5 Aug 2018 13:07:38 -0300 Subject: [PATCH 05/16] Finished Proxy vs. Obeject.defineProperty --- Framework/framework-br.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Framework/framework-br.md b/Framework/framework-br.md index 37d69938..8b4a4186 100644 --- a/Framework/framework-br.md +++ b/Framework/framework-br.md @@ -172,20 +172,19 @@ function defineReactive(obj, key, val) { A implementação acima é um simples two-way binding. A idéia central é manualmente disparar o getter das propriedades para adicionar o Publish/Subscribe. - ## Proxy vs. Obeject.defineProperty -Although `Obeject.defineProperty` has been able to implement two-way binding, it is still flawed. +Apesar do `Obeject.defineProperty` ser capaz de implementar o two-way binding, ele ainda é falho. -* It can only implement data hijacking on properties, so it needs deep traversal of the entire object -* it can't listen to changes in data for arrays +* Ele consegue implementar apenas o sequestro de dados nas propriedades, +* ele não consegue escutar a mudança de dados para arrays -Although Vue can detect the changes in array data, it is actually a hack and is flawed. +Apesar de Vue conseguir detectar mudanças em um array de dados, é na verdade um hack e é falho. ```js const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) -// hack the following functions +// hack as seguintes funções const methodsToPatch = [ 'push', 'pop', @@ -196,10 +195,10 @@ const methodsToPatch = [ 'reverse' ] methodsToPatch.forEach(function (method) { - // get the native function + // obter a função nativa const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { - // call the native function + // chama a função nativa const result = original.apply(this, args) const ob = this.__ob__ let inserted @@ -213,14 +212,15 @@ methodsToPatch.forEach(function (method) { break } if (inserted) ob.observeArray(inserted) - // trigger the update + // dispara uma atualização ob.dep.notify() return result }) }) ``` -On the other hand, `Proxy` doesn't have the above problem. It natively supports listening to array changes and can intercept the entire object directly, so Vue will also replace `Obeject.defineProperty` with `Proxy` in the next big version. +Por outro lado, `Proxy` não tem o problema acima. Ele suporta nativamente a escuta para mudança no array e consegue interceptar o objeto completo diretamente, então Vue irá também substituir `Obeject.defineProperty` por `Proxy` na próxima grande versão. + ```js let onWatch = (obj, setBind, getLogger) => { @@ -244,8 +244,8 @@ let p = onWatch(obj, (v) => { }, (target, property) => { console.log(`Get '${property}' = ${target[property]}`); }) -p.a = 2 // bind `value` to `2` -p.a // -> Get 'a' = 2 +p.a = 2 // vincula `value` para `2` +p.a // -> obtém 'a' = 2 ``` # Routing principle From 780c8ff7a0003e58ac03a379adcda40e4b80b938 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 5 Aug 2018 13:40:27 -0300 Subject: [PATCH 06/16] Finished Routing principle && Virtual DOM concept --- Framework/framework-br.md | 46 +++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Framework/framework-br.md b/Framework/framework-br.md index 8b4a4186..c9422e63 100644 --- a/Framework/framework-br.md +++ b/Framework/framework-br.md @@ -248,19 +248,19 @@ p.a = 2 // vincula `value` para `2` p.a // -> obtém 'a' = 2 ``` -# Routing principle +# Princípio de rotas -The front-end routing is actually very simple to implement. The essence is to listen to changes in the URL, then match the routing rules, display the corresponding page, and no need to refresh. Currently, there are only two implementations of the route used by a single page. +As rotas no front-end é atualmente simples de implementar. A essência é escutar as mudanças na URL, então coincidir com as regras de roteamento, exibindo a página correspondente, e não precisa atualizar. Atualmente, existe apenas duas implementações de rotas usados pela página única. -- hash mode -- history mode +- modo hash +- modo history -`www.test.com/#/` is the hash URL. When the hash value after `#` changes, no request will be sent to server. You can listen to the URL change through the `hashchange` event, and then jump to the corresponding page. +`www.test.com/#/` é a hash URL. Quando o valor depois do hash `#` muda, nenhuma request será enviada ao servidor. Você pode escutar as mudanças na URL através do evento `hashchange`, e então pular para a página correspondente. ![](https://user-gold-cdn.xitu.io/2018/7/13/1649266be7ec2fb7?w=981&h=546&f=png&s=36646) -History mode is a new feature of HTML5, which is more beautiful than Hash URL. +O modo history é uma nova funcionalidade do HTML5, do qual é muito mais lindo que o Hash URL. ![](https://user-gold-cdn.xitu.io/2018/7/13/164926dc15df79d3?w=1244&h=546&f=png&s=49647) @@ -268,27 +268,27 @@ History mode is a new feature of HTML5, which is more beautiful than Hash URL. [source code](https://github.com/KieSun/My-wheels/tree/master/Virtual%20Dom) -## Why Virtual Dom is needed +## Por que Virtual Dom é preciso -As we know, modifying DOM is a costly task. We could consider using JS objects to simulate DOM objects, since operating on JS objects is much more time saving than operating on DOM. +Como nós sabemos, modificar o DOM é uma tarefa custosa. Poderiamos considerar usar objetos JS para simular objetos DOM, desde de que operando em objetos JS é economizado muito mais tempo que operar no DOM. -For example +Por exemplo ```js -// Let's assume this array simulates a ul which contains five li's. +// Vamos assumir que esse array simula um ul do qual cotém 5 li's. [1, 2, 3, 4, 5] -// using this to replace the ul above. +// usando esse para substituir a ul acima. [1, 2, 5, 4] ``` -From the above example, it's apparent that the first ul's 3rd li is removed, and the 4th and the 5th are exchanged positions. +A partir do exemplo acima, é aparente que a terceira li foi removida, e a quarta e quinta mudaram suas posições -If the previous operation is applied to DOM, we have the following code: +Se a operação anterior for aplicada no DOM, nós temos o seguinte código: ```js -// removing the 3rd li +// removendo a terceira li ul.childNodes[2].remove() -// interexchanging positions between the 4th and the 5th +// trocando internamente as posições do quarto e quinto elemento let fromNode = ul.childNodes[4] let toNode = node.childNodes[3] let cloneFromNode = fromNode.cloneNode(true) @@ -297,11 +297,11 @@ ul.replaceChild(cloneFromNode, toNode) ul.replaceChild(cloenToNode, fromNode) ``` -Of course, in actual operations, we need an indentifier for each node, as an index for checking if two nodes are identical. This is why both Vue and React's official documentation suggests using a unique identifier `key` for nodes in a list to ensure efficiency. +De fato, nas operações atuais, nós precisamos de um identificador para cada nó, como um index para verificar se os dois nós são idênticos. Esse é o motivo de ambos Vue e React sugerirem na documentação oficial usar identificadores `key` para os nós em uma lista para garantir eficiência. -DOM element can not only be simulated, but they can also be rendered by JS objects. +Elementos do DOM não só podem ser simulados, mas eles também podem ser renderizados por objetos JS. -Below is a simple implementation of a JS object simulating a DOM element. +Abaixo está uma simples implementação de um objeto JS simulando um elemento DOM. ```js export default class Element { @@ -322,7 +322,7 @@ export default class Element { } if (key) this.key = key } - // render + // renderização render() { let root = this._createElement( this.tag, @@ -336,11 +336,11 @@ export default class Element { create() { return this._createElement(this.tag, this.props, this.children, this.key) } - // create an element + // criando um elemento _createElement(tag, props, child, key) { - // create an element with tag + // criando um elemento com tag let el = document.createElement(tag) - // set properties on the element + // definindo propriedades em um elemento for (const key in props) { if (props.hasOwnProperty(key)) { const value = props[key] @@ -350,7 +350,7 @@ export default class Element { if (key) { el.setAttribute('key', key) } - // add children nodes recursively + // adicionando nós filhos recursivamente if (child) { child.forEach(element => { let child From c68ee6146c1f21fe567803c459be915227c7d804 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 5 Aug 2018 14:04:13 -0300 Subject: [PATCH 07/16] Finished Virtual Dom algorithm introduction --- Framework/framework-br.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Framework/framework-br.md b/Framework/framework-br.md index c9422e63..1d339668 100644 --- a/Framework/framework-br.md +++ b/Framework/framework-br.md @@ -372,18 +372,19 @@ export default class Element { } ``` -## Virtual Dom algorithm introduction +## Introdução ao algoritmo de Virtual Dom -The next step after using JS to implement DOM element is to detect object changes. +O próximo passo depois de usar JS para implementar elementos DOM é detectar mudanças no objeto. -DOM is a multi-branching tree. If we were to compare the old and the new trees thoroughly, the time complexity would be O(n ^ 3), which is simply unacceptable. Therefore, the React team optimized their algorithm to achieve an O(n) complexity for detecting changes. +DOM é uma árvore de multi-ramifacações. Se nós compararmos a antiga e a nova árvore completamente, o tempo de complexidade seria O(n ^ 3), o que é simplesmente inaceitável. Assim sendo, o time do React otimizou esse algoritimo para alcançar uma complexidade O(n) para detectar as mudanças. -The key to achieving O(n) is to only compare the nodes on the same level rather than across levels. This works because in actual usage we rarely move DOM elements across levels. +A chave para alcançar O(n) é apenas comparar os +nós no mesmo nível em vez de através dos níveis. Isso funciona porque no uso atual nós raramente movemos elementos DOM através dos níveis. -We then have two steps of the algorithm. +Nós então temos dois passos do algoritmo. -- from top to bottom, from left to right to iterate the object, aka depth first search. This step adds an index to every node, for rendering the differences later. -- whenever a node has a child element, we check whether the child element changed. +- Do topo para fundo, da esquerda para direita itera o objeto, primeira pesquisa de profundidade. Nesse passo adicionamos um índice para cada nó, renderizando as diferenças depois. +- sempre que um nó tiver um elemento filho, nós verificamos se o elemento filho mudou. ## Virtual Dom algorithm implementation From 96928792f6a48d157a290d95a0c4fc25cf37bb6c Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 5 Aug 2018 14:24:54 -0300 Subject: [PATCH 08/16] Finished recursion of the tree --- Framework/framework-br.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Framework/framework-br.md b/Framework/framework-br.md index 1d339668..449449f8 100644 --- a/Framework/framework-br.md +++ b/Framework/framework-br.md @@ -386,44 +386,44 @@ Nós então temos dois passos do algoritmo. - Do topo para fundo, da esquerda para direita itera o objeto, primeira pesquisa de profundidade. Nesse passo adicionamos um índice para cada nó, renderizando as diferenças depois. - sempre que um nó tiver um elemento filho, nós verificamos se o elemento filho mudou. -## Virtual Dom algorithm implementation +## Implementação do algoritmo do Virtual Dom -### recursion of the tree +### recursão da árvore -First let's implement the recursion algorithm of the tree. Before doing that, let's consider the different cases of comparing two nodes. +Primeiro vamos implementar o algoritmo de recursão da árvore. Antes de fazer isso, vamos considerar os diferentes casos de comparar dois nós. -1. new nodes's `tagName` or `key` is different from that of the old one. This menas the old node is replaced, and we don't have to recurse on the node any more because the whole subtree is removed. -2. new node's `tagName` and `key` (maybe nonexistent) are the same as the old's. We start recursing on the subtree. -3. no new node appears. No operation needed. +1. novos nós `tagName` ou `key` são diferentes do antigo. Isso significa que o nó antigo é substituido, e nós não temos que recorrer no nó mais porque a subárvore foi completamente removida. +2. novos nós `tagName` e `key` (talvez inexistente) são a mesma do antigo. Nós começamos a recursar na subárvore. +3. não aparece novo nó. Não é preciso uma operação. ```js import { StateEnums, isString, move } from './util' import Element from './element' export default function diff(oldDomTree, newDomTree) { - // for recording changes + // para gravar mudanças let pathchs = {} - // the index starts at 0 + // o índice começa no 0 dfs(oldDomTree, newDomTree, 0, pathchs) return pathchs } function dfs(oldNode, newNode, index, patches) { - // for saving the subtree changes + // para salvar as mudanças na subárvore let curPatches = [] - // three cases - // 1. no new node, do nothing - // 2. new nodes' tagName and `key` are different from the old one's, replace - // 3. new nodes' tagName and key are the same as the old one's, start recursing + // três casos + // 1. não é novo nó, não faça nada + // 2. novos nós tagName e `key` são diferentes dos antigos, substitua + // 3. novos nós tagName e chave são o mesmo do antigo, comece a recursão if (!newNode) { } else if (newNode.tag === oldNode.tag && newNode.key === oldNode.key) { - // check whether properties changed + // verifique se as propriedades mudaram let props = diffProps(oldNode.props, newNode.props) if (props.length) curPatches.push({ type: StateEnums.ChangeProps, props }) - // recurse the subtree + // recurse a subárvore diffChildren(oldNode.children, newNode.children, index, patches) } else { - // different node, replace + // diferentes nós, substitua curPatches.push({ type: StateEnums.Replace, node: newNode }) } From 99004d62671efd3d3a8b7c781c8db19f2dec4bc9 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 6 Aug 2018 09:31:44 -0300 Subject: [PATCH 09/16] Finished Algorithm Implementation for Detecting List Changes --- Framework/framework-br.md | 72 +++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/Framework/framework-br.md b/Framework/framework-br.md index 449449f8..25262a4f 100644 --- a/Framework/framework-br.md +++ b/Framework/framework-br.md @@ -42,7 +42,7 @@ Embora a verificação suja ser ineficiente, ele consegue completar a tarefa sem ## Sequesto de dados -Vuew internamente usa `Obeject.defineProperty()` para implementar o two-way binding, do qual permite você escutar por eventos de `set` e `get`. +Vue internamente usa `Obeject.defineProperty()` para implementar o two-way binding, do qual permite você escutar por eventos de `set` e `get`. ```js var data = { name: 'yck' } @@ -414,7 +414,7 @@ function dfs(oldNode, newNode, index, patches) { // três casos // 1. não é novo nó, não faça nada // 2. novos nós tagName e `key` são diferentes dos antigos, substitua - // 3. novos nós tagName e chave são o mesmo do antigo, comece a recursão + // 3. novos nós tagName e key são o mesmo do antigo, comece a recursão if (!newNode) { } else if (newNode.tag === oldNode.tag && newNode.key === oldNode.key) { // verifique se as propriedades mudaram @@ -437,20 +437,20 @@ function dfs(oldNode, newNode, index, patches) { } ``` -### checking property changes +### verificando mudança das propriedades -We also have three steps for checking for property changes +Nós temos também três passos para verificar por mudanças nas propriedades -1. iterate the old property list, check if the property still exists in the new property list. -2. iterate the new property list, check if there are changes for properties existing in both lists. -3. for the second step, also check if a property doesn't exist in the old property list. +1. itere a lista de propriedades antiga, verifique se a propriedade ainda existe na nova lista de propriedade. +2. itere a nova lista de propriedades, verifique se existe mudanças para propriedades existente nas duas listas. +3. no segundo passo, também verifique se a propriedade não existe na lista de propriedades antiga. ```js function diffProps(oldProps, newProps) { - // three steps for checking for props - // iterate oldProps for removed properties - // iterate newProps for chagned property values - // lastly check if new properties are added + // três passos para checar as props + // itere oldProps para remover propriedades + // itere newProps para mudar os valores das propriedades + // por último verifique se novas propriedades foram adicionadas let change = [] for (const key in oldProps) { if (oldProps.hasOwnProperty(key) && !newProps[key]) { @@ -479,30 +479,30 @@ function diffProps(oldProps, newProps) { } ``` -### Algorithm Implementation for Detecting List Changes +### Implementação do Algoritmo de detecção de mudanças na lista -This algorithm is the core of the Virtual Dom. Let's go down the list. -The main steps are similar to checking property changes. There are also three steps. +Esse algoritmo é o núcle do Virtual Dom. Vamos descer a lista. +O passo principal é similar a verificação de mudanças nas propriedades. Também existe três passos. -1. iterate the old node list, check if the node still exists in the new list. -2. iterate the new node list, check if there is any new node. -3. for the second step, also check if a node moved. +1. itere a antiga lista de nós, verifique se ao nó ainda existe na nova lista. +2. itere a nova lista de nós, verifiquen se existe algum novo nó. +3. para o seguindo passo, também verifique se o nó moveu. -PS: this algorithm only handles nodes with `key`s. +PS: esse algoritmo apenas manipula nós com `key`s. ```js function listDiff(oldList, newList, index, patches) { - // to make the iteration more convenient, first take all keys from both lists + // para fazer a iteração mais conveniente, primeiro pegue todas as chaves de ambas as listas let oldKeys = getKeys(oldList) let newKeys = getKeys(newList) let changes = [] - // for saving the node daa after changes - // there are several advantages of using this array to save - // 1. we can correctly obtain the index of the deleted node - // 2. we only need to operate on the DOM once for interexchanged nodes - // 3. we only need to iterate for the checking in the `diffChildren` function - // we don't need to check again for nodes existing in both lists + // para salvar o dado do nó depois das mudanças + // existe varia vantagem de usar esse array para salvar + // 1. nós conseguimos obter corretamente o index de nós deletados + // 2. precisamos apenas opera no DOM uma vez para interexchanged os nós + // 3. precisamos apenas iterar para verificar na função `diffChildren` + // nós não precisamos verificar de novo para nós existente nas duas listas let list = [] oldList && oldList.forEach(item => { @@ -510,19 +510,19 @@ function listDiff(oldList, newList, index, patches) { if (isString(item)) { key = item } - // checking if the new children has the current node - // if not then delete + // verificando se o novo filho tem o nó atual + // se não, então delete let index = newKeys.indexOf(key) if (index === -1) { list.push(null) } else list.push(key) }) - // array after iterative changes + // array depois de alterações iterativas let length = list.length - // since deleting array elements changes the indices - // we remove from the back to make sure indices stay the same + // uma vez deletando um array de elementos, o índice muda + // removemos de trás para ter certeza que os índices permanecem o mesmo for (let i = length - 1; i >= 0; i--) { - // check if the current element is null, if so then it means we need to remove it + // verifica se o elemento atual é null, se sim então significa que precisamos remover ele if (!list[i]) { list.splice(i, 1) changes.push({ @@ -531,17 +531,17 @@ function listDiff(oldList, newList, index, patches) { }) } } - // iterate the new list, check if a node is added or moved - // also add and move nodes for `list` + // itere a nova lista, verificando se um nó é adicionado ou movido + // também adicione ou mova nós para `list` newList && newList.forEach((item, i) => { let key = item.key if (isString(item)) { key = item } - // check if the old children has the current node + // verifique se o filho antigo tem o nó atual let index = list.indexOf(key) - // if not then we need to insert + // se não então precisamos inserir if (index === -1 || key == null) { changes.push({ type: StateEnums.Insert, @@ -550,7 +550,7 @@ function listDiff(oldList, newList, index, patches) { }) list.splice(i, 0, key) } else { - // found the node, need to check if it needs to be moved. + // encontrado o nó, precisamos verificar se ele precisar ser movido. if (index !== i) { changes.push({ type: StateEnums.Move, From 13aae0f69635fad5010e39fae58ab3f8b023980b Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 6 Aug 2018 09:44:28 -0300 Subject: [PATCH 10/16] Finished framework-br --- Framework/framework-br.md | 42 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Framework/framework-br.md b/Framework/framework-br.md index 25262a4f..e35f4126 100644 --- a/Framework/framework-br.md +++ b/Framework/framework-br.md @@ -581,14 +581,14 @@ function getKeys(list) { } ``` -### Iterating and Marking Child Elements +### Iterando e marcando elementos filho -For this function, there are two main functionalities. +Para essa função, existe duas principais funcionalidades. -1. checking differences between two lists -2. marking nodes +1. verificando diferenças entre duas listas +2. marcando nós -In general, the functionalities impelemented are simple. +No geral, a implementação das funcionalidades são simples. ```js function diffChildren(oldChild, newChild, index, patches) { @@ -600,7 +600,7 @@ function diffChildren(oldChild, newChild, index, patches) { patches[index] = changes } } - // marking last iterated node + // marcando o ultimo nó iterado let last = null oldChild && oldChild.forEach((item, i) => { @@ -610,8 +610,8 @@ function diffChildren(oldChild, newChild, index, patches) { last && last.children ? index + last.children.length + 1 : index + 1 let keyIndex = list.indexOf(item.key) let node = newChild[keyIndex] - // only iterate nodes existing in both lists - // no need to visit the added or removed ones + // só itera nós existentes em ambas as listas + // não precisamos visitar os adicionados ou removidos if (node) { dfs(item, node, index, patches) } @@ -621,23 +621,23 @@ function diffChildren(oldChild, newChild, index, patches) { } ``` -### Rendering Difference +### Renderizando diferenças -From the earlier algorithms, we can already get the differences between two trees. After knowing the differences, we need to locally update DOM. Let's take a look at the last step of Virtual Dom algorithms. +A partir dos algoritmos anteriores, nós já obtemos as diferenças entre duas árvores. Depois de saber as diferenças, precisamos atualizar o DOM localmente. Vamos dar uma olhada no último passo do algoritmo do Virtual Dom. -Two main functionalities for this function +Há duas funcionalidades principais para isso -1. Deep search the tree and extract the nodes needing modifications -2. Locally update DOM +1. Busca profunda na árvore e extrair os nós que precisam ser modificados. +2. Atualize o DOM local -This code snippet is pretty easy to understand as a whole. +Esse pedaço de código é bastante fácil de entender como um todo. ```js let index = 0 export default function patch(node, patchs) { let changes = patchs[index] let childNodes = node && node.childNodes - // this deep search is the same as the one in diff algorithm + // essa busca profunda é a mesma do algoritmo de diff if (!childNodes) index += 1 if (changes && changes.length && patchs[index]) { changeDom(node, changes) @@ -698,13 +698,13 @@ function changeDom(node, changes, noChild) { } ``` -## The End +## Fim -The implementation of the Virtual Dom algorithms contains the following three steps: +A implementação dos algoritimos do Virtual Dom contém os três seguintes passos: -1. Simulate the creation of DOM objects through JS -2. Check differences between two objects -3. Render the differences +1. Simular a criação de objetos DOM através do JS +2. Verifica a diferança entre dois objetos +2. Renderiza a diferença ```js let test4 = new Element('div', { class: 'my-div' }, ['test4']) @@ -726,4 +726,4 @@ setTimeout(() => { }, 1000) ``` -Although the current implementation is simple, it's definitely enough for understanding Virtual Dom algorithms. \ No newline at end of file +Embora a implementação atual seja simples, isso não é definitivamente o suficiente para ententer os algoritmos do Virtual Dom. \ No newline at end of file From 93358f555086e67d4ad7825b8dec721529dfb067 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 7 Aug 2018 13:29:54 -0300 Subject: [PATCH 11/16] Started vue-br --- Framework/vue-br.md | 206 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 Framework/vue-br.md diff --git a/Framework/vue-br.md b/Framework/vue-br.md new file mode 100644 index 00000000..eb45442e --- /dev/null +++ b/Framework/vue-br.md @@ -0,0 +1,206 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Análises do princípio NextTick](#nexttick-principle-analysis) +- [Análises do ciclo de vid](#lifecycle-analysis) + + + +# Análises do princípio NextTick + +`nextTick` permiti-nos adiar a callback ser executada depois da próxima atualizada do clico do DOM, para obter a atualização. + +Antes da versão 2.4, Vue usou micro tarefas, mas prioridade das micro tarefas é bem alta, e em alguns casos, isso deve ser mais rápido que o evento de bubbling, mas se você usar macro tarefas, pode haver alguns problemas de performance na renderização. Então na nova versão, micro tarefas são usadas por padrão, mas macro tarefas serão usadas em casos especiais, como v-on. + +For implementing macrotasks, you will first determine if `setImmediate` can be used, if not, downgrade to `MessageChannel`. If not again, use `setTimeout`. + +```js +if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { + macroTimerFunc = () => { + setImmediate(flushCallbacks) + } +} else if ( + typeof MessageChannel !== 'undefined' && + (isNative(MessageChannel) || + // PhantomJS + MessageChannel.toString() === '[object MessageChannelConstructor]') +) { + const channel = new MessageChannel() + const port = channel.port2 + channel.port1.onmessage = flushCallbacks + macroTimerFunc = () => { + port.postMessage(1) + } +} else { + /* istanbul ignore next */ + macroTimerFunc = () => { + setTimeout(flushCallbacks, 0) + } +} +``` + +`nextTick` also supports the use of `Promise`, which will determine whether `Promise` is implemented. + +```js +export function nextTick(cb?: Function, ctx?: Object) { + let _resolve + // Consolidate callback functions into an array + callbacks.push(() => { + if (cb) { + try { + cb.call(ctx) + } catch (e) { + handleError(e, ctx, 'nextTick') + } + } else if (_resolve) { + _resolve(ctx) + } + }) + if (!pending) { + pending = true + if (useMacroTask) { + macroTimerFunc() + } else { + microTimerFunc() + } + } + // Determine if Promise can be used + // Assign _resolve if possible + // This way the callback function can be called in the way of promise + if (!cb && typeof Promise !== 'undefined') { + return new Promise(resolve => { + _resolve = resolve + }) + } +} +``` + +# Lifecycle analysis + +The lifecycle function is the hook function that the component will trigger when it initializes or updates the data. + +![](https://user-gold-cdn.xitu.io/2018/7/12/1648d9df78201f07?w=1200&h=3039&f=png&s=50021) + +The following code will be called at initialization, and lifecycle is called by `callHook` + +```js +Vue.prototype._init = function(options) { + initLifecycle(vm) + initEvents(vm) + initRender(vm) + callHook(vm, 'beforeCreate') // can not get props data + initInjections(vm) + initState(vm) + initProvide(vm) + callHook(vm, 'created') +} +``` + +It can be found that in the above code when `beforeCreate` is called, the data in `props` or `data` cannot be obtained because the initialization of these data is in `initState`. + +Next, the mount function will be called + +```js +export function mountComponent { + callHook(vm, 'beforeMount') + // ... + if (vm.$vnode == null) { + vm._isMounted = true + callHook(vm, 'mounted') + } +} +``` + +`beforeMount` will be executed before mounting the instance, then starts to create the VDOM and replace it with the real DOM, and finally call the `mounted` hook. And there’s a judgment logic here that if it is an external `new Vue({}) `, `$vnode` doesn’t exist, so the `mounted` hook will be executed directly. If there are child components, they will be mounted recursively, only when all the child components are mounted, the mount hooks of the root components will be executed. + +Next, it comes to the hook function that will be called when the data is updated. + +```js +function flushSchedulerQueue() { + // ... + for (index = 0; index < queue.length; index++) { + watcher = queue[index] + if (watcher.before) { + watcher.before() // call `beforeUpdate` + } + id = watcher.id + has[id] = null + watcher.run() + // in dev build, check and stop circular updates. + if (process.env.NODE_ENV !== 'production' && has[id] != null) { + circular[id] = (circular[id] || 0) + 1 + if (circular[id] > MAX_UPDATE_COUNT) { + warn( + 'You may have an infinite update loop ' + + (watcher.user + ? `in watcher with expression "${watcher.expression}"` + : `in a component render function.`), + watcher.vm + ) + break + } + } + } + callUpdatedHooks(updatedQueue) +} + +function callUpdatedHooks(queue) { + let i = queue.length + while (i--) { + const watcher = queue[i] + const vm = watcher.vm + if (vm._watcher === watcher && vm._isMounted) { + callHook(vm, 'updated') + } + } +} +``` + +There are two lifecycle functions that aren’t mentioned in the above diagram, `activated` and `deactivated`, and only the `kee-alive` component has these two life cycles. Components wrapped with `keep-alive` will not be destroyed during the switch, but be cached in memory and execute the `deactivated` hook function, and execute the `actived` hook function after matching the cache and rendering. + +Finally, let’s see the hook function that used to destroy the component. + +```js +Vue.prototype.$destroy = function() { + // ... + callHook(vm, 'beforeDestroy') + vm._isBeingDestroyed = true + // remove self from parent + const parent = vm.$parent + if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { + remove(parent.$children, vm) + } + // teardown watchers + if (vm._watcher) { + vm._watcher.teardown() + } + let i = vm._watchers.length + while (i--) { + vm._watchers[i].teardown() + } + // remove reference from data ob + // frozen object may not have observer. + if (vm._data.__ob__) { + vm._data.__ob__.vmCount-- + } + // call the last hook... + vm._isDestroyed = true + // invoke destroy hooks on current rendered tree + vm.__patch__(vm._vnode, null) + // fire destroyed hook + callHook(vm, 'destroyed') + // turn off all instance listeners. + vm.$off() + // remove __vue__ reference + if (vm.$el) { + vm.$el.__vue__ = null + } + // release circular reference (#6759) + if (vm.$vnode) { + vm.$vnode.parent = null + } +} +``` + +The `beforeDestroy` hook function will be called before the destroy operation is performed, and then a series of destruction operations are performed. If there are child components, they will be destroyed recursively, and only when all the child components are destroyed, the hook `destroyed` of the root component will be executed. From ad24b2d7c413a9414dbc2dac8cbe9f52a011cab0 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 8 Aug 2018 09:41:38 -0300 Subject: [PATCH 12/16] Finished NextTick principle --- Framework/vue-br.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Framework/vue-br.md b/Framework/vue-br.md index eb45442e..5bc5b863 100644 --- a/Framework/vue-br.md +++ b/Framework/vue-br.md @@ -13,7 +13,7 @@ Antes da versão 2.4, Vue usou micro tarefas, mas prioridade das micro tarefas é bem alta, e em alguns casos, isso deve ser mais rápido que o evento de bubbling, mas se você usar macro tarefas, pode haver alguns problemas de performance na renderização. Então na nova versão, micro tarefas são usadas por padrão, mas macro tarefas serão usadas em casos especiais, como v-on. -For implementing macrotasks, you will first determine if `setImmediate` can be used, if not, downgrade to `MessageChannel`. If not again, use `setTimeout`. +Para implementar macro tarefas, você primeiro deve determinar se o `setImmediate` pode ser usado, se não, abaixe para `MessageChannel`. Se não novamente, use `setTimeout`. ```js if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { @@ -40,12 +40,12 @@ if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { } ``` -`nextTick` also supports the use of `Promise`, which will determine whether `Promise` is implemented. +`nextTick` também suporta o uso de `Promise`, do qual ira determinar se a `Promise` está implemented. ```js export function nextTick(cb?: Function, ctx?: Object) { let _resolve - // Consolidate callback functions into an array + // Consolidade funções de callback dentro do de um array callbacks.push(() => { if (cb) { try { @@ -65,9 +65,9 @@ export function nextTick(cb?: Function, ctx?: Object) { microTimerFunc() } } - // Determine if Promise can be used - // Assign _resolve if possible - // This way the callback function can be called in the way of promise + // Determina se a Promisse pode ser usada + // Atribuir _resolve se possivel + // Desta maneira a função callback pode ser chamada em forma de promise if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve From f61b365b99ff32fe9db9b5e61fcb7f2d099c8546 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 8 Aug 2018 09:55:23 -0300 Subject: [PATCH 13/16] Partial completed vue --- Framework/vue-br.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Framework/vue-br.md b/Framework/vue-br.md index 5bc5b863..1af07684 100644 --- a/Framework/vue-br.md +++ b/Framework/vue-br.md @@ -40,12 +40,12 @@ if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { } ``` -`nextTick` também suporta o uso de `Promise`, do qual ira determinar se a `Promise` está implemented. +`nextTick` também suporta o uso de `Promise`, do qual ira determinar se a `Promise` está implementada. ```js export function nextTick(cb?: Function, ctx?: Object) { let _resolve - // Consolidade funções de callback dentro do de um array + // Consolide funções de callback dentro do de um array callbacks.push(() => { if (cb) { try { @@ -76,20 +76,20 @@ export function nextTick(cb?: Function, ctx?: Object) { } ``` -# Lifecycle analysis +# Análise do Ciclo de Vida -The lifecycle function is the hook function that the component will trigger when it initializes or updates the data. +A função do ciclo de vida é a função gancho que o componente vai disparar quando inicilaizar ou atualizar os dados. ![](https://user-gold-cdn.xitu.io/2018/7/12/1648d9df78201f07?w=1200&h=3039&f=png&s=50021) -The following code will be called at initialization, and lifecycle is called by `callHook` +O seguinte código irá ser chamado na inicialização, e o ciclo de vida vai ser chamado pelo `callHook` ```js Vue.prototype._init = function(options) { initLifecycle(vm) initEvents(vm) initRender(vm) - callHook(vm, 'beforeCreate') // can not get props data + callHook(vm, 'beforeCreate') // não consegue receber dados das props initInjections(vm) initState(vm) initProvide(vm) @@ -97,9 +97,9 @@ Vue.prototype._init = function(options) { } ``` -It can be found that in the above code when `beforeCreate` is called, the data in `props` or `data` cannot be obtained because the initialization of these data is in `initState`. +Ele pode ser encontrado no código acima quando `beforeCreate` é chamado, o dado no `props` ou `data` não pode ser obtido porque a inicialização desse dado está no `initState`. -Next, the mount function will be called +No próximo, a função motadora vai ser chamada ```js export function mountComponent { @@ -112,9 +112,9 @@ export function mountComponent { } ``` -`beforeMount` will be executed before mounting the instance, then starts to create the VDOM and replace it with the real DOM, and finally call the `mounted` hook. And there’s a judgment logic here that if it is an external `new Vue({}) `, `$vnode` doesn’t exist, so the `mounted` hook will be executed directly. If there are child components, they will be mounted recursively, only when all the child components are mounted, the mount hooks of the root components will be executed. +`beforeMount` vai ser executado antes de montar uma instância, então comece a criar o VDOM e substituir ele com o DOM real, e finalmente chame o gancho `mounted`. E há um julgamente lógico aqui, que se ele for um `new Vue({}) ` externo, `$vnode` não existe, então o gancho `mounted` será executado diretamente. Se existe um componente filho, ele vai ser montado recursivamente, apenas quando todos os componentes filhos forem montados, o gancho de montar o componente raíz vai ser executado. -Next, it comes to the hook function that will be called when the data is updated. +Próximo, isso vem para a função gancho que vai ser chamada quando os dados forem atualizados. ```js function flushSchedulerQueue() { From f0714063e08ada1e2f78bd071f63d212f20401d9 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 9 Aug 2018 10:18:56 -0300 Subject: [PATCH 14/16] Finished translate of framework to Pt-Br --- Framework/vue-br.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Framework/vue-br.md b/Framework/vue-br.md index 1af07684..93eaa29f 100644 --- a/Framework/vue-br.md +++ b/Framework/vue-br.md @@ -122,12 +122,12 @@ function flushSchedulerQueue() { for (index = 0; index < queue.length; index++) { watcher = queue[index] if (watcher.before) { - watcher.before() // call `beforeUpdate` + watcher.before() // chama `beforeUpdate` } id = watcher.id has[id] = null watcher.run() - // in dev build, check and stop circular updates. + // no dev build, verifca e para check and stop circular updates. if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { @@ -157,21 +157,21 @@ function callUpdatedHooks(queue) { } ``` -There are two lifecycle functions that aren’t mentioned in the above diagram, `activated` and `deactivated`, and only the `kee-alive` component has these two life cycles. Components wrapped with `keep-alive` will not be destroyed during the switch, but be cached in memory and execute the `deactivated` hook function, and execute the `actived` hook function after matching the cache and rendering. +Existem duas funções do ciclo de vida que não são mencionada no diagrama acima, `activated` e `deactivated`, e apenas o componente `kee-alive` possui esses dois ciclos. Componente encapsulado com `keep-alive` não serão destruídos durante o switch, mas sera cacheado em memória e executado a função gancho `deactivated`, e executar a função `actived` depois de coincidir o cache e a renderização. -Finally, let’s see the hook function that used to destroy the component. +Finalmente, vamos olhar a função gancho usada para destruir o componente. ```js Vue.prototype.$destroy = function() { // ... callHook(vm, 'beforeDestroy') vm._isBeingDestroyed = true - // remove self from parent + // remove se mesmo a partir do pai const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) } - // teardown watchers + // destroi watchers if (vm._watcher) { vm._watcher.teardown() } @@ -179,28 +179,28 @@ Vue.prototype.$destroy = function() { while (i--) { vm._watchers[i].teardown() } - // remove reference from data ob - // frozen object may not have observer. + // remove a referência a partir do dado ob + // objeto congelados não devem ter um observador. if (vm._data.__ob__) { vm._data.__ob__.vmCount-- } - // call the last hook... + // chame o último gancho... vm._isDestroyed = true - // invoke destroy hooks on current rendered tree - vm.__patch__(vm._vnode, null) - // fire destroyed hook + // invoque ganchos destruídos na árvore atualmente renderizada + // dispare o gancho destruído callHook(vm, 'destroyed') - // turn off all instance listeners. + // desligo todos os ouvintes da instância. vm.$off() // remove __vue__ reference + // remove __vue__ reference if (vm.$el) { vm.$el.__vue__ = null } - // release circular reference (#6759) + // lance uma referência circular (#6759) if (vm.$vnode) { vm.$vnode.parent = null } } ``` -The `beforeDestroy` hook function will be called before the destroy operation is performed, and then a series of destruction operations are performed. If there are child components, they will be destroyed recursively, and only when all the child components are destroyed, the hook `destroyed` of the root component will be executed. +A função `beforeDestroy` será chamada antes da operação de destruir ser desempenhada, e então uma série de operações de destruição são desempenhadas. Se existe um componente filho, eles serão destruidos recursivamente, e apenas quando todos os componente filhos são destruídos, o gancho `destroyed` do componente raíz será executado. From e910a4e204ea31fe442e185d626f0a3e60a0104a Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 9 Aug 2018 18:55:53 -0300 Subject: [PATCH 15/16] UPDATE vue br --- Framework/vue-br.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Framework/vue-br.md b/Framework/vue-br.md index 93eaa29f..91687541 100644 --- a/Framework/vue-br.md +++ b/Framework/vue-br.md @@ -9,7 +9,7 @@ # Análises do princípio NextTick -`nextTick` permiti-nos adiar a callback ser executada depois da próxima atualizada do clico do DOM, para obter a atualização. +`nextTick` permiti-nos adiar a callback ser executada depois da próxima atualizada do ciclo do DOM, para obter a atualização. Antes da versão 2.4, Vue usou micro tarefas, mas prioridade das micro tarefas é bem alta, e em alguns casos, isso deve ser mais rápido que o evento de bubbling, mas se você usar macro tarefas, pode haver alguns problemas de performance na renderização. Então na nova versão, micro tarefas são usadas por padrão, mas macro tarefas serão usadas em casos especiais, como v-on. @@ -78,7 +78,7 @@ export function nextTick(cb?: Function, ctx?: Object) { # Análise do Ciclo de Vida -A função do ciclo de vida é a função gancho que o componente vai disparar quando inicilaizar ou atualizar os dados. +A função do ciclo de vida é a função gancho que o componente vai disparar quando inicializar ou atualizar os dados. ![](https://user-gold-cdn.xitu.io/2018/7/12/1648d9df78201f07?w=1200&h=3039&f=png&s=50021) @@ -99,7 +99,7 @@ Vue.prototype._init = function(options) { Ele pode ser encontrado no código acima quando `beforeCreate` é chamado, o dado no `props` ou `data` não pode ser obtido porque a inicialização desse dado está no `initState`. -No próximo, a função motadora vai ser chamada +No próximo, a função montadora vai ser chamada ```js export function mountComponent { @@ -166,7 +166,7 @@ Vue.prototype.$destroy = function() { // ... callHook(vm, 'beforeDestroy') vm._isBeingDestroyed = true - // remove se mesmo a partir do pai + // remove-se mesmo a partir do pai const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) From af8ce4fb96e4b773afaac7fd8b1af203ac26f339 Mon Sep 17 00:00:00 2001 From: Lucas Date: Fri, 10 Aug 2018 09:14:32 -0300 Subject: [PATCH 16/16] Started translation to JS-br --- JS/JS-br.md | 1400 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1400 insertions(+) create mode 100644 JS/JS-br.md diff --git a/JS/JS-br.md b/JS/JS-br.md new file mode 100644 index 00000000..e4ff55f5 --- /dev/null +++ b/JS/JS-br.md @@ -0,0 +1,1400 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Tipos incorporados](#built-in-types) +- [Conversão de tipo](#type-conversion) + - [Convertendo para boleano](#converting-to-boolean) + - [De objetos para tipos primitivos](#objects-to-primitive-types) + - [Operadores aritméticos](#arithmetic-operators) + - [`==` operador](#-operator) + - [Operador de comparação](#comparison-operator) +- [Typeof](#typeof) +- [New](#new) +- [This](#this) +- [Instanceof](#instanceof) +- [Scope](#scope) +- [Closure](#closure) +- [Prototypes](#prototypes) +- [Herança](#inheritance) +- [Cópia rasa e profunda](#deep-and-shallow-copy) + - [Cópia rasa](#shallow-copy) + - [Deep copy](#deep-copy) +- [Modularização](#modularization) + - [CommonJS](#commonjs) + - [AMD](#amd) +- [A diferença entre call, apply, bind](#the-differences-between-call-apply-bind) + - [simulação para implementar `call` e `apply`](#simulation-to-implement--call-and--apply) +- [Implementação de Promise](#promise-implementation) +- [Implementação do Generator](#generator-implementation) +- [Debouncing](#debouncing) +- [Throttle](#throttle) +- [Map、FlatMap e Reduce](#mapflatmap-and-reduce) +- [Async e await](#async-and-await) +- [Proxy](#proxy) +- [Por que 0.1 + 0.2 != 0.3](#why-01--02--03) +- [Expressões regulares](#regular-expressions) + - [Metacaracteres](#metacharacters) + - [Flags](#flags) + - [Character Shorthands](#character-shorthands) + + + +# Tipos incorparados +O JavaScript define sete tipos incorporados, dos quais podem ser divididos em duas categorias `Primitive Type` e `Object`. + +Existem seis tipos primitivos: `null`, `undefined`, `boolean`, `number`, `string` e `symbol `. + +Em JavaScript, não existe inteiros de verdade, todos os números são implementados em dupla-precisão 64-bit em formato binário IEEE 754. Quando nós usamos números de pontos flutuantes, iremos ter alguns efeitos colaterais. Aqui está um exemplo desses efeitos colaterais. + +```js +0.1 + 0.2 == 0.3 // false +``` + +Para tipos primitivos, quando usamos literais para inicializar uma variável, ela te apenas um valor literal, ela não tem um tipo. Isso será convertido para o tipo correspondente apenas quando necessário. + +```js +let a = 111 // apenas literais, não um número +a.toString() // convertido para o objeto quando necessário +``` + +Objeto é um tipo de referência. Nós iremos encontrar problemas sobre cópia rasa e cópia profunda quando usando ele. + +```js +let a = { name: 'FE' } +let b = a +b.name = 'EF' +console.log(a.name) // EF +``` + +# Type Conversion + +## Converting to Boolean + +When the condition is judged, other than `undefined`, `null`, `false`, `NaN`, `''`, `0`, `-0`, all of the values, including objects, are converted to `true`. + +## Objects to Primitive Types + +When objects are converted, `valueOf` and `toString` will be called, respectively in order. These two methods can also be overridden. + +```js +let a = { + valueOf() { + return 0 + } +} +``` + +## Arithmetic Operators + +Only for additions, if one of the parameters is a string, the other one will be converted to string as well. For all other operations, as long as one of the parameters is a number, the other one will be converted to a number. + +Additions will invoke three types of type conversions: to primitive types, to numbers and to string: + +```js +1 + '1' // '11' +2 * '2' // 4 +[1, 2] + [2, 1] // '1,22,1' +// [1, 2].toString() -> '1,2' +// [2, 1].toString() -> '2,1' +// '1,2' + '2,1' = '1,22,1' +``` + +Note the expression `'a' + + 'b'` for addition: + +```js +'a' + + 'b' // -> "aNaN" +// since + 'b' -> NaN +// You might have seen + '1' -> 1 +``` + +## `==` operator + +![](https://user-gold-cdn.xitu.io/2018/3/30/16275cb21f5b19d7?w=1630&h=1208&f=png&s=496784) + +`toPrimitive` in above figure is converting objects to primitive types. + +`===` is usually recommended to compare values. However, if you would like to check for `null` value, you can use `xx == null`. + +Let's take a look at an example `[] == ![] // -> true`. The following process explains why the expression evaluates to `true`: + +```js +// [] converting to true, then take the opposite to false +[] == false +// with #8 +[] == ToNumber(false) +[] == 0 +// with #10 +ToPrimitive([]) == 0 +// [].toString() -> '' +'' == 0 +// with #6 +0 == 0 // -> true +``` + +## Comparison Operator + +1. If it's an object, `toPrimitive` is used. +2. If it's a string, `unicode` character index is used. + +# Typeof + +`typeof` can always display the correct type of primitive types, except `null`: +```js +typeof 1 // 'number' +typeof '1' // 'string' +typeof undefined // 'undefined' +typeof true // 'boolean' +typeof Symbol() // 'symbol' +typeof b // b is not declared,but it still can be displayed as undefined +``` + +For object, `typeof` will always display `object`, except **function**: +```js +typeof [] // 'object' +typeof {} // 'object' +typeof console.log // 'function' +``` + +As for `null`, it is always be treated as an `object` by `typeof`,although it is a primitive data type, and this is a bug that has been around for a long time. +```js +typeof null // 'object' +``` + +Why does this happen? Because the initial version of JS was based on 32-bit systems, which stored type information of variables in the lower bits for performance considerations. Those start with `000` are objects, and all the bits of `null` are zero, so it is erroneously treated as an object. Although the current code of checking internal types has changed, this bug has been passed down. + +We can use `Object.prototype.toString.call(xx)` if we want to get the correct data type of a variable, and then we can get a string like `[Object Type]`: + +```js +let a +// We can also judge `undefined` like this +a === undefined +// But the nonreserved word `undefined` can be re-assigned in a lower version browser +let undefined = 1 +// it will go wrong to judge like this +// So we can use the following method, with less code +// it will always return `undefined`, whatever follows `void ` +a === void 0 +``` + +# New + +1. Create a new object +2. Chained to prototype +3. Bind this +4. Return a new object + +The above four steps will happen in the process of calling `new`. We can also try to implement `new ` by ourselves: + +```js +function create() { + // Create an empty object + let obj = new Object() + // Get the constructor + let Ctor = [].shift.call(arguments) + // Chained to prototype + obj.__proto__ = Ctor.prototype + // Bind this, Execute the constructor + let result = Con.apply(obj, arguments) + // Make sure the new one is an object + return typeof result === 'object'? result : obj +} +``` + +Instances of object are all created with `new`, whether it's `function Foo()` , or `let a = { b: 1 }` . + +It is recommended to create objects using the literal notation (whether it's for performance or readability), since a look-up is needed for `Object` through the scope chain when creating an object using `new Object()`, but you don't have this kind of problem when using literals. + +```js +function Foo() {} +// Function is a syntactic sugar +// Internally equivalent to new Function() +let a = { b: 1 } +// Inside this literal, `new Object()` is also used +``` + +For `new`, we also need pay attention to the operator precedence: + +```js +function Foo() { + return this; +} +Foo.getName = function () { + console.log('1'); +}; +Foo.prototype.getName = function () { + console.log('2'); +}; + +new Foo.getName(); // -> 1 +new Foo().getName(); // -> 2 +``` + +![](https://user-gold-cdn.xitu.io/2018/4/9/162a9c56c838aa88?w=2100&h=540&f=png&s=127506) + +As you can see from the above image, `new Foo()` has a higher priority than `new Foo`, so we can divide the execution order of the above code like this: + + +```js +new (Foo.getName()); +(new Foo()).getName(); +``` + +For the first function, `Foo.getName()` is executed first, so the result is 1; +As for the latter, it first executes `new Foo()` to create an instance, then finds the `getName` function on `Foo` via the prototype chain, so the result is 2. + +# This + +`This`, a concept that is confusing to many peole, is actually not difficult to understand as long as you remember the following rules: + +```js +function foo() { + console.log(this.a); +} +var a = 1; +foo(); + +var obj = { + a: 2, + foo: foo +}; +obj.foo(); + +// In the above two situations, `this` only depends on the object before calling the function, +// and the second case has higher priority than the first case . + +// the following scenario has the highest priority,`this` will only be bound to c, +// and there's no way to change what `this` is bound to . + +var c = new foo(); +c.a = 3; +console.log(c.a); + +// finally, using `call`, `apply`, `bind` to change what `this` is bound to, +// is another scenario where its priority is only second to `new` +``` + +Understanding the above several situations, we won’t be confused by `this` under most circumstances. Next, let’s take a look at `this` in arrow functions: + +```js +function a() { + return () => { + return () => { + console.log(this); + }; + }; +} +console.log(a()()()); +``` +Actually, the arrow function does not have `this`, `this` in the above function only depends on the first outer function that is not an arrow function. For this case, `this` is default to `window` because calling `a` matches the first condition in the above codes. Also, what `this` is bound to will not be changed by any codes once `this` is bound to the context. + + +# Instanceof + +The `instanceof` operator can correctly check the type of objects, because its internal mechanism is to find out if `prototype` of this type can be found in the prototype chain of the object. + +let’s try to implement it: +```js +function instanceof(left, right) { + // get the `prototype` of the type + let prototype = right.prototype + // get the `prototype` of the object + left = left.__proto__ + // check if the type of the object is equal to the prototype of the type + while (true) { + if (left === null) + return false + if (prototype === left) + return true + left = left.__proto__ + } +} +``` + +# Scope + +Executing JS code would generate execution context, as long as code is not written in a function, it belongs to the global execution context. Code in a function will generate function execution context. There’s also an `eval` execution context, which basically is not used anymore, so you can think of only two execution contexts. + +The `[[Scope]]` attribute is generated in the first stage of generating execution context, which is a pointer, corresponds to the linked list of the scope, and JS will look up variables through this linked list up to the global context. + +Let's look at a common example , `var`: + +```js +b() // call b +console.log(a) // undefined + +var a = 'Hello world' + +function b() { + console.log('call b') +} +``` + +It’s known that function and variable hoisting is the real reason for the above outputs. The usual explanation for hoisting says that the declarations are ‘moved’ to the top of the code, and there is nothing wrong with that and it’s easy for everyone to understand. But a more accurate explanation should be something like this: + +There would be two stages when the execution context is generated. The first stage is the stage of creation(to be specific, the step of generating variable objects), in which the JS interpreter would find out variables and functions that need to be hoisted, and allocate memory for them in advance, then functions would be stored into memory entirely, but variables would only be declared and assigned to `undefined`, therefore, we can use them in advance in the second stage (the code execution stage). + +In the process of hoisting, the same function would overwrite the last function, and functions have higher priority than variables hoisting. + +```js +b() // call b second + +function b() { + console.log('call b fist') +} +function b() { + console.log('call b second') +} +var b = 'Hello world' +``` + +Using `var` is more likely error-prone, thus ES6 introduces a new keyword `let`. `let` has an important feature that it can’t be used before declared, which conflicts the common saying that `let` doesn’t have the ability of hoisting. In fact, `let` hoists declaration, but is not assigned, because the **temporal dead zone**. + +# Closure + +The definition of closure is simple: function A returns a function B, and function B can access variables of function A, thus function B is called a closure. + +```js +function A() { + let a = 1 + function B() { + console.log(a) + } + return B +} +``` + +Are you wondering why function B can also refer to variables in function A while function A has been popped up from the call stack? Because variables in function A are stored on the heap at this time. The current JS engine can identify which variables need to be stored on the heap and which need to be stored on the stack by escape analysis. + +A classic interview question is using closures in loops to solve the problem of using `var` to define functions: + +```js +for ( var i=1; i<=5; i++) { + setTimeout( function timer() { + console.log( i ); + }, i*1000 ); +) +``` + +First of all, all loops will be executed completely because `setTimeout` is an asynchronous function, and at that time `i` is 6, so it will print a bunch of 6. + +There are three solutions,closure is the first one: + +```js +for (var i = 1; i <= 5; i++) { + (function(j) { + setTimeout(function timer() { + console.log(j); + }, j * 1000); + })(i); +} +``` + +The second one is to make use of the third parameter of `setTimeout`: + +```js +for ( var i=1; i<=5; i++) { + setTimeout( function timer(j) { + console.log( j ); + }, i*1000, i); +} +``` + +The third is to define `i` using `let`: + +```js +for ( let i=1; i<=5; i++) { + setTimeout( function timer() { + console.log( i ); + }, i*1000 ); +} +``` + +For `let`, it will create a block-level scope, which is equivalent to: + +```js +{ + // Form block-level scope + let i = 0 + { + let ii = i + setTimeout( function timer() { + console.log( i ); + }, i*1000 ); + } + i++ + { + let ii = i + } + i++ + { + let ii = i + } + ... +} +``` + +# Prototypes + +![](https://camo.githubusercontent.com/71cab2efcf6fb8401a2f0ef49443dd94bffc1373/68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f31332f313632316538613962636230383732643f773d34383826683d35393026663d706e6726733d313531373232) + +Each function, besides `Function.prototype.bind()`, has an internal property, denoted as `prototype`, which is a reference to the prototype. + +Each object has an internal property, denoted as `__proto__`, which is a reference to the prototype of the constructor that creates the object. This property actually refers to `[[prototype]]`, but `[[prototype]]` is an internal property that we can’t access, so we use `__proto__` to access it. + +Objects can use `__proto__` to look up properties that do not belong to the object, and `__proto__` connects objects together to form a prototype chain. + + +# Inheritance + +In ES5, we can solve the problems of inheritance by using the following ways: + +```js +function Super() {} +Super.prototype.getNumber = function() { + return 1 +} + +function Sub() {} +let s = new Sub() +Sub.prototype = Object.create(Super.prototype, { + constructor: { + value: Sub, + enumerable: false, + writable: true, + configurable: true + } +}) +``` + +The above idea of inheritance implementation is to set the `prototype` of the child class as the `prototype` of the parent class. + +In ES6, we can easily solve this problem with the `class` syntax: + +```js +class MyDate extends Date { + test() { + return this.getTime() + } +} +let myDate = new MyDate() +myDate.test() +``` + +However, ES6 is not compatible with all browsers, so we need to use Babel to compile this code. + +If call `myDate.test()` with compiled code, you’ll be surprised to see that there’s an error: + +![](https://user-gold-cdn.xitu.io/2018/3/28/1626b1ecb39ab20d?w=678&h=120&f=png&s=32812) + +Because there are restrictions on the low-level of JS, if the instance isn’t constructed by `Date`, it can’t call functions in `Date`, which also explains from another aspect that `Class` inheritance in ES6 is different from the general inheritance in ES5 syntax. + +Since the low-level of JS limits that the instance must be constructed by `Date` , we can try another way to implement inheritance: + +```js +function MyData() { + +} +MyData.prototype.test = function () { + return this.getTime() +} +let d = new Date() +Object.setPrototypeOf(d, MyData.prototype) +Object.setPrototypeOf(MyData.prototype, Date.prototype) +``` + +The implementation idea of the above inheritance: first create the instance of parent class => change the original `__proto__` of the instance, connect it to the `prototype` of child class => change the `__proto__` of child class’s `prototype` to the `prototype` of parent class. + +The inheritance implement with the above method can perfectly solve the restriction on low-level of JS. + + +# Deep and Shallow Copy + +```js +let a = { + age: 1 +} +let b = a +a.age = 2 +console.log(b.age) // 2 +``` + +From the above example, we can see that if you assign an object to a variable, then the values of both will be the same reference, one changes, the other changes accordingly. + +Usually, we don't want such problem to appear during development, thus we can use shallow copy to solve this problem. + +## Shallow copy + +Firstly we can solve the problem by `Object.assign`: +```js +let a = { + age: 1 +} +let b = Object.assign({}, a) +a.age = 2 +console.log(b.age) // 1 +``` + +Certainly, we can use the spread operator (...) to solve the problem: +```js +let a = { + age: 1 +} +let b = {...a} +a.age = 2 +console.log(b.age) // 1 +``` + +Usually, shallow copy can solve most problems, but we need deep copy when encountering the following situation: +```js +let a = { + age: 1, + jobs: { + first: 'FE' + } +} +let b = {...a} +a.jobs.first = 'native' +console.log(b.jobs.first) // native +``` +The shallow copy only solves the problem of the first layer. If the object contains objects, then it returns to the beginning topic that both values share the same reference. To solve this problem, we need to introduce deep copy. + +## Deep copy + +The problem can usually be solved by `JSON.parse(JSON.stringify(object))` + +```js +let a = { + age: 1, + jobs: { + first: 'FE' + } +} +let b = JSON.parse(JSON.stringify(a)) +a.jobs.first = 'native' +console.log(b.jobs.first) // FE +``` + +But this method also has its limits: +* ignore `undefined` +* unable to serialize function +* unable to resolve circular references in an object +```js +let obj = { + a: 1, + b: { + c: 2, + d: 3, + }, +} +obj.c = obj.b +obj.e = obj.a +obj.b.c = obj.c +obj.b.d = obj.b +obj.b.e = obj.b.c +let newObj = JSON.parse(JSON.stringify(obj)) +console.log(newObj) +``` + +If an object is circularly referenced like the above example, you’ll find the method `JSON.parse(JSON.stringify(object))` can’t make a deep copy of this object: + +![](https://user-gold-cdn.xitu.io/2018/3/28/1626b1ec2d3f9e41?w=840&h=100&f=png&s=30123) + +When dealing with function or `undefined`, the object can also not be serialized properly. +```js +let a = { + age: undefined, + jobs: function() {}, + name: 'yck' +} +let b = JSON.parse(JSON.stringify(a)) +console.log(b) // {name: "yck"} +``` + +In above case, you can see that the method ignores function and `undefined`. + +Most often complex data can be serialized, so this method can solve most problems, and as a built-in function, it has the fastest performance when dealing with deep copy. Certainly, you can use [the deep copy function of `lodash` ](https://lodash.com/docs#cloneDeep) when your data contains the above three cases. + +If the object you want to copy contains a built-in type but doesn’t contain a function, you can use `MessageChannel` +```js +function structuralClone(obj) { + return new Promise(resolve => { + const {port1, port2} = new MessageChannel(); + port2.onmessage = ev => resolve(ev.data); + port1.postMessage(obj); + }); +} + +var obj = {a: 1, b: { + c: b +}} +// pay attention that this method is asynchronous +// it can handle `undefined` and circular reference object +const clone = await structuralClone(obj); +``` + +# Modularization + +With Babel, we can directly use ES6's modularization: + +```js +// file a.js +export function a() {} +export function b() {} +// file b.js +export default function() {} + +import {a, b} from './a.js' +import XXX from './b.js' +``` + +## CommonJS + +`CommonJS` is Node's unique feature. `Browserify` is needed for `CommonJS` to be used in browsers. + +```js +// a.js +module.exports = { + a: 1 +} +// or +exports.a = 1 + +// b.js +var module = require('./a.js') +module.a // -> log 1 +``` + +In the code above, `module.exports` and `exports` can cause confusions. Let us take a peek at the internal implementations: + +```js +var module = require('./a.js') +module.a +// this is actually a wrapper of a function to be executed immediately so that we don't mess up the global variables. +// what's important here is that module is a Node only variable. +module.exports = { + a: 1 +} +// basic implementation +var module = { + exports: {} // exports is an empty object +} +// This is why exports and module.exports have similar usage. +var exports = module.exports +var load = function (module) { + // to be exported + var a = 1 + module.exports = a + return module.exports +}; +``` + +Let's then talk about `module.exports` and `exports`, which have similar usage, but one cannot assign a value to `exports` directly. The assignment would be a no-op. + +The differences between the modularizations in `CommonJS` and in ES6 are: + +- The former supports dynamic imports, which is `require(${path}/xx.js)`; the latter doesn't support it yet, but there have been proposals. +- The former uses synchronous imports. Since it is used on the server end and files are local, it doesn't matter much even if the synchronous imports block the main thread. The latter uses asynchronous imports, because it is used in browsers in which file downloads are needed. Rendering process would be affected much if asynchronous import was used. +- The former copies the values when exporting. Even if the values exported change, the values imported will not change. Therefore, if values shall be updated, another import needs to happen. However, the latter uses realtime bindings, the values imported and exported point to the same memory addresses, so the imported values change along with the exported ones. +- In execution the latter is compiled to `require/exports`. + +## AMD + +AMD is brought forward by `RequireJS`. + +```js +// AMD +define(['./a', './b'], function(a, b) { + a.do() + b.do() +}) +define(function(require, exports, module) { + var a = require('./a') + a.doSomething() + var b = require('./b') + b.doSomething() +}) +``` + +# The differences between call, apply, bind + +Firstly, let’s tell the difference between the former two. + +Both `call` and `apply` are used to change what `this` refers to. Their role is the same, but the way to pass parameters is different. + +In addition to the first parameter, `call` can accept an argument list, while `apply` accepts a single array of arguments. + +```js +let a = { + value: 1 +} +function getValue(name, age) { + console.log(name) + console.log(age) + console.log(this.value) +} +getValue.call(a, 'yck', '24') +getValue.apply(a, ['yck', '24']) +``` + +## simulation to implement `call` and `apply` + +We can consider how to implement them from the following rules: + +* If the first parameter isn’t passed, then the first parameter will default to `window`; +* Change what `this` refers to, which makes new object capable of executing the function. Then let’s think like this: add a function to a new object and then delete it after the execution. + +```js +Function.prototype.myCall = function (context) { + var context = context || window + // Add an property to the `context` + // getValue.call(a, 'yck', '24') => a.fn = getValue + context.fn = this + // take out the rest parameters of `context` + var args = [...arguments].slice(1) + // getValue.call(a, 'yck', '24') => a.fn('yck', '24') + var result = context.fn(...args) + // delete fn + delete context.fn + return result +} +``` + +The above is the main idea of simulating `call`, and the implementation of `apply` is similar. + +```js +Function.prototype.myApply = function (context) { + var context = context || window + context.fn = this + + var result + // There's a need to determine whether to store the second parameter + // If the second parameter exists, spread it + if (arguments[1]) { + result = context.fn(...arguments[1]) + } else { + result = context.fn() + } + + delete context.fn + return result +} +``` + +The role of `bind` is the same as the other two, except that it returns a function. And we can implement currying with `bind` + +let’s simulate `bind`: + +```js +Function.prototype.myBind = function (context) { + if (typeof this !== 'function') { + throw new TypeError('Error') + } + var _this = this + var args = [...arguments].slice(1) + // return a function + return function F() { + // we can use `new F()` because it returns a function, so we need to determine + if (this instanceof F) { + return new _this(...args, ...arguments) + } + return _this.apply(context, args.concat(...arguments)) + } +} +``` + +# Promise implementation + +`Promise` is a new syntax introduced by ES6, which resolves the problem of callback hell. + +Promise can be seen as a state machine and it's initial state is `pending`. We can change the state to `resolved` or `rejected` by using the `resolve` and `reject` functions. Once the state is changed, it cannot be changed again. + +The function `then` returns a Promise instance, which is a new instance instead of the previous one. And that's because the Promise specification states that in addition to the `pending` state, other states cannot be changed, and multiple calls of function `then` will be meaningless if the same instance is returned. + +For `then`, it can essentially be seen as `flatMap`: + +```js +// three states +const PENDING = 'pending'; +const RESOLVED = 'resolved'; +const REJECTED = 'rejected'; +// promise accepts a function argument that will execute immediately. +function MyPromise(fn) { + let _this = this; + _this.currentState = PENDING; + _this.value = undefined; + // To save the callback of `then`,only cached when the state of the promise is pending, + // at most one will be cached in every instance + _this.resolvedCallbacks = []; + _this.rejectedCallbacks = []; + + _this.resolve = function(value) { + // execute asynchronously to guarantee the execution order + setTimeout(() => { + if (value instanceof MyPromise) { + // if value is a Promise, execute recursively + return value.then(_this.resolve, _this.reject) + } + if (_this.currentState === PENDING) { + _this.currentState = RESOLVED; + _this.value = value; + _this.resolvedCallbacks.forEach(cb => cb()); + } + }) + } + + _this.reject = function(reason) { + // execute asynchronously to guarantee the execution order + setTimeout(() => { + if (_this.currentState === PENDING) { + _this.currentState = REJECTED; + _this.value = reason; + _this.rejectedCallbacks.forEach(cb => cb()); + } + }) + } + + // to solve the following problem + // `new Promise(() => throw Error('error))` + try { + fn(_this.resolve, _this.reject); + } catch (e) { + _this.reject(e); + } +} + +MyPromise.prototype.then = function(onResolved, onRejected) { + const self = this; + // specification 2.2.7, `then` must return a new promise + let promise2; + // specification 2.2, both `onResolved` and `onRejected` are optional arguments + // it should be ignored if `onResolved` or `onRjected` is not a function, + // which implements the penetrate pass of it's value + // `Promise.resolve(4).then().then((value) => console.log(value))` + onResolved = typeof onResolved === 'function' ? onResolved : v => v; + onRejected = typeof onRejected === 'function' ? onRejected : r => throw r; + + if (self.currentState === RESOLVED) { + return (promise2 = new MyPromise((resolve, reject) => { + // specification 2.2.4, wrap them with `setTimeout`, + // in order to insure that `onFulfilled` and `onRjected` execute asynchronously + setTimeout(() => { + try { + let x = onResolved(self.value); + resolutionProcedure(promise2, x, resolve, reject); + } catch (reason) { + reject(reason); + } + }); + })); + } + + if (self.currentState === REJECTED) { + return (promise2 = new MyPromise((resolve, reject) => { + // execute `onRejected` asynchronously + setTimeout(() => { + try { + let x = onRejected(self.value); + resolutionProcedure(promise2, x, resolve, reject); + } catch (reason) { + reject(reason); + } + }); + })) + } + + if (self.currentState === PENDING) { + return (promise2 = new MyPromise((resolve, reject) => { + self.resolvedCallbacks.push(() => { + // Considering that it may throw error, wrap them with `try/catch` + try { + let x = onResolved(self.value); + resolutionProcedure(promise2, x, resolve, reject); + } catch (r) { + reject(r); + } + }); + + self.rejectedCallbacks.push(() => { + try { + let x = onRejected(self.value); + resolutionProcedure(promise2, x, resolve, reject); + } catch (r) { + reject(r); + } + }) + })) + } +} + +// specification 2.3 +function resolutionProcedure(promise2, x, resolve, reject) { + // specification 2.3.1,`x` and `promise2` can't refer to the same object, + // avoiding the circular references + if (promise2 === x) { + return reject(new TypeError('Error')); + } + + // specification 2.3.2, if `x` is a Promise and the state is `pending`, + // the promise must remain, If not, it should execute. + if (x instanceof MyPromise) { + if (x.currentState === PENDING) { + // call the function `resolutionProcedure` again to + // confirm the type of the argument that x resolves + // If it's a primitive type, it will be resolved again to + // pass the value to next `then`. + x.then((value) => { + resolutionProcedure(promise2, value, resolve, reject); + }, reject) + } else { + x.then(resolve, reject); + } + return; + } + + // specification 2.3.3.3.3 + // if both `reject` and `resolve` are executed, the first successful + // execution takes precedence, and any further executions are ignored + let called = false; + // specification 2.3.3, determine whether `x` is an object or a function + if (x !== null && (typeof x === 'object' || typeof x === 'function')) { + // specification 2.3.3.2, if can't get `then`, execute the `reject` + try { + // specification 2.3.3.1 + let then = x.then; + // if `then` is a function, call the `x.then` + if (typeof then === 'function') { + // specification 2.3.3.3 + then.call(x, y => { + if (called) return; + called = true; + // specification 2.3.3.3.1 + resolutionProcedure(promise2, y, resolve, reject); + }, e => { + if (called) return; + called = true; + reject(e); + }); + } else { + // specification 2.3.3.4 + resolve(x); + } + } catch (e) { + if (called) return; + called = true; + reject(e); + } + } else { + // specification 2.3.4, `x` belongs to primitive data type + resolve(x); + } +} +``` + +The above codes, which is implemented based on the Promise / A+ specification, can pass the full test of `promises-aplus-tests` + +![](https://user-gold-cdn.xitu.io/2018/3/29/162715e8e37e689d?w=1164&h=636&f=png&s=300285) + +# Generator Implementation + +Generator is an added syntactic feature in ES6. Similar to `Promise`, it can be used for asynchronous programming. + +```js +// * means this is a Generator function +// yield within the block can be used to pause the execution +// next can resume execution +function* test() { + let a = 1 + 2; + yield 2; + yield 3; +} +let b = test(); +console.log(b.next()); // > { value: 2, done: false } +console.log(b.next()); // > { value: 3, done: false } +console.log(b.next()); // > { value: undefined, done: true } +``` + +As we can tell from the above code, a function with a `*` would have the `next` function execution. In other words, the execution of the function returns an object. Every call to the `next` function can resume executing the paused code. A simple implementation of the Generator function is shown below: + +```js +// cb is the compiled 'test' function +function generator(cb) { + return (function() { + var object = { + next: 0, + stop: function() {} + }; + + return { + next: function() { + var ret = cb(object); + if (ret === undefined) return { value: undefined, done: true }; + return { + value: ret, + done: false + }; + } + }; + })(); +} +// After babel's compilation, 'test' function turns into this: +function test() { + var a; + return generator(function(_context) { + while (1) { + switch ((_context.prev = _context.next)) { + // yield splits the code into several blocks + // every 'next' call executes one block of clode + // and indicates the next block to execute + case 0: + a = 1 + 2; + _context.next = 4; + return 2; + case 4: + _context.next = 6; + return 3; + // execution complete + case 6: + case "end": + return _context.stop(); + } + } + }); +} +``` + +# Debouncing + +Have you ever encountered this problem in your development: how to do a complex computation in a scrolling event or to prevent the "second accidental click" on a button? + +These requirements can be achieved with function debouncing. Especially for the first one, if complex computations are carried out in frequent event callbacks, there's a large chance that the page becomes laggy. It's better to combine multiple computations into a single one, and only operate at particular time. Since there are many libraries that implement debouncing, we won't build our own here and will just take underscore's source code to explain debouncing: + +```js +/** + * underscore's debouncing function. When the callback function is called in series, func will only execute when the idel time is larger or equal to `wait`. + * + * @param {function} func callback function + * @param {number} wait length of waiting intervals + * @param {boolean} immediate when set to true, func is executed immediately + * @return {function} returns the function to be called by the client + */ +_.debounce = function(func, wait, immediate) { + var timeout, args, context, timestamp, result; + + var later = function() { + // compare now to the last timestamp + var last = _.now() - timestamp; + // if the current time interval is smaller than the set interval and larger than 0, then reset the timer. + if (last < wait && last >= 0) { + timeout = setTimeout(later, wait - last); + } else { + // otherwise it's time to execute the callback function + timeout = null; + if (!immediate) { + result = func.apply(context, args); + if (!timeout) context = args = null; + } + } + }; + + return function() { + context = this; + args = arguments; + // obtain the timestamp + timestamp = _.now(); + // if the timer doesn't exist then execute the function immediately + var callNow = immediate && !timeout; + // if the timer doesn't exist then create one + if (!timeout) timeout = setTimeout(later, wait); + if (callNow) { + // if the immediate execution is needed, use apply to start the function + result = func.apply(context, args); + context = args = null; + } + + return result; + }; + }; +``` + +The complete function implementation is not too difficult. Let's summarize here. + +- For the implementation of protecting against accidental clicks: as long as I start a timer and the timer is there, no matter how you click the button, the callback function won't be executed. Whenever the timer ends and is set to `null`, another click is allowed. +- For the implementation of a delayed function execution: every call to the debouncing function will trigger an evaluation of time interval between the current call and the last one. If the interval is less than the required, another timer will be created, and the delay is set to the set interval minus the previous elapsed time. When the time's up, the corresponding callback function is executed. + +# Throttle + +`Debounce` and `Throttle` are different in nature. `Debounce` is to turn multiple executions into one last execution, and `Throttle` is to turn multiple executions into executions at regular intervals. + +```js +// The first two parameters with debounce are the same function +// options: You can pass two properties +// trailing: Last time does not execute +// leading: First time does not execute +// The two properties cannot coexist, otherwise the function cannot be executed +_.throttle = function(func, wait, options) { + var context, args, result; + var timeout = null; + // Previous timestamp + var previous = 0; + // Set empty if options is not passed + if (!options) options = {}; + // Timer callback function + var later = function() { + // If you set `leading`, then set `previous` to zero + // The first `if` statement of the following function is used + previous = options.leading === false ? 0 : _.now(); + // The first is prevented memory leaks and the second is judged the following timers when setting `timeout` to null + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + return function() { + // Get current timestamp + var now = _.now(); + // It must be true when it entering firstly + // If you do not need to execute the function firstly + // Set the last timestamp to current + // Then it will be greater than 0 when the remaining time is calculated next + if (!previous && options.leading === false) + previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + // This condition will only be entered if it set `trailing` + // This condition will be entered firstly if it not set `leading` + // Another point, you may think that this condition will not be entered if you turn on the timer + // In fact, it will still enter because the timer delay is not accurate + // It is very likely that you set 2 seconds, but it needs 2.2 seconds to trigger, then this time will enter this condition + if (remaining <= 0 || remaining > wait) { + // Clean up if there exist a timer otherwise it call twice callback + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + // Judgment whether timer and trailing are set + // And you can't set leading and trailing at the same time + timeout = setTimeout(later, remaining); + } + return result; + }; +}; +``` + +# Map、FlatMap and Reduce + +The effect of `Map` is to generate a new array, iterate over the original array, take each element out to do some transformation, and then `append` to the new array. + +```js +[1, 2, 3].map((v) => v + 1) +// -> [2, 3, 4] +``` + +`Map` has three parameters, namely the current index element, the index, the original array. + +```js +['1','2','3'].map(parseInt) +// parseInt('1', 0) -> 1 +// parseInt('2', 1) -> NaN +// parseInt('3', 2) -> NaN +``` + +The effect of `FlatMap` is almost the same with a `Map`, but the original array will be flatten for multidimensional arrays. You can think of `FlatMap` as a `map` and a `flatten`, which is currently not supported in browsers. + +```js +[1, [2], 3].flatMap((v) => v + 1) +// -> [2, 3, 4] +``` + +You can achieve this when you want to completely reduce the dimensions of a multidimensional array: + +```js +const flattenDeep = (arr) => Array.isArray(arr) + ? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , []) + : [arr] + +flattenDeep([1, [[2], [3, [4]], 5]]) +``` + +The effect of `Reduce` is to combine the values in the array and get a final value: + +```js +function a() { + console.log(1); +} + +function b() { + console.log(2); +} + +[a, b].reduce((a, b) => a(b())) +// -> 2 1 +``` + + +# Async and await + +`async` function will return a `Promise`: + +```js +async function test() { + return "1"; +} +console.log(test()); // -> Promise {: "1"} +``` + +You can think of `async` as wrapping a function using `Promise.resolve()`. + +`await` can only be used in `async` functions: + +```js +function sleep() { + return new Promise(resolve => { + setTimeout(() => { + console.log('finish') + resolve("sleep"); + }, 2000); + }); +} +async function test() { + let value = await sleep(); + console.log("object"); +} +test() +``` + +The above code will print `finish` before printing `object`. Because `await` waits for the `sleep` function `resolve`, even if the synchronization code is followed, it is not executed before the asynchronous code is executed. + +The advantage of `async` and `await` compared to the direct use of `Promise` lies in handling the call chain of `then`, which can produce clear and accurate code. The downside is that misuse of `await` can cause performance problems because `await` blocks the code. Perhaps the asynchronous code does not depend on the former, but it still needs to wait for the former to complete, causing the code to lose concurrency. + +Let's look at a code that uses `await`: + +```js +var a = 0 +var b = async () => { + a = a + await 10 + console.log('2', a) // -> '2' 10 + a = (await 10) + a + console.log('3', a) // -> '3' 20 +} +b() +a++ +console.log('1', a) // -> '1' 1 +``` + +You may have doubts about the above code, here we explain the principle: + +- First the function `b` is executed. The variable `a` is still 0 before execution `await 10`, Because the `Generators` are implemented inside `await` and `Generators` will keep things in the stack, so at this time `a = 0` is saved +- Because `await` is an asynchronous operation, `console.log('1', a)` will be executed first. +- At this point, the synchronous code is completed and asynchronous code is started. The saved value is used. At this time, `a = 10` +- Then comes the usual code execution + +# Proxy + +Proxy is a new feature since ES6. It can be used to define operations in objects: + +```js +let p = new Proxy(target, handler); +// `target` represents the object of need to add the proxy +// `handler` customizes operations in the object +``` + +Proxy can be handy for implementation of data binding and listening: + +```js +let onWatch = (obj, setBind, getLogger) => { + let handler = { + get(target, property, receiver) { + getLogger(target, property) + return Reflect.get(target, property, receiver); + }, + set(target, property, value, receiver) { + setBind(value); + return Reflect.set(target, property, value); + } + }; + return new Proxy(obj, handler); +}; + +let obj = { a: 1 } +let value +let p = onWatch(obj, (v) => { + value = v +}, (target, property) => { + console.log(`Get '${property}' = ${target[property]}`); +}) +p.a = 2 // bind `value` to `2` +p.a // -> Get 'a' = 2 +``` + +# Why 0.1 + 0.2 != 0.3 + +Because JS uses the IEEE 754 double-precision version (64-bit). Every language that uses this standard has this problem. + +As we know, computers use binaries to represent decimals, so `0.1` in binary is represented as + +```js +// (0011) represents cycle +0.1 = 2^-4 * 1.10011(0011) +``` + +How do we come to this binary number? We can try computing it as below: + +![](https://user-gold-cdn.xitu.io/2018/4/26/162ffcb7fc1ca5a9?w=800&h=1300&f=png&s=83139) + +Binary computations in float numbers are different from those in integers. For multiplications, only the float bits are computed, while the integer bits are used for the binaries for each bit. Then the first bit is used as the most significant bit. Therefore we get `0.1 = 2^-4 * 1.10011(0011)`. + +`0.2` is similar. We just need to get rid of the first multiplcation and get `0.2 = 2^-3 * 1.10011(0011)`. + +Back to the double float for IEEE 754 standard. Among the 64 bits, one bit is used for signing, 11 used for integer bits, and the rest 52 bits are floats. Since `0.1` and `0.2` are infinitely cycling binaries, the last bit of the floats needs to indicate whether to round (same as rounding in decimals). + +After rounding, `2^-4 * 1.10011...001` becomes `2^-4 * 1.10011(0011 * 12 times)010`. After adding these two binaries we get `2^-2 * 1.0011(0011 * 11 times)0100`, which is `0.30000000000000004` in decimals. + +The native solution to this problem is shown below: + +```js +parseFloat((0.1 + 0.2).toFixed(10)) +``` + +# Regular Expressions + +## Metacharacters + +| Metacharacter | Effect | +| :-----------: | :----------------------------------------------------------: | +| . | matches any character except line terminators: \n, \r, \u2028 or \u2029. | +| [] | matches anything within the brackets. For example, [0-9] can match any number | +| ^ | ^9 means matching anything that starts with '9'; [`^`9] means not matching characters except '9' in between brackets | +| {1, 2} | matches 1 or 2 digit characters | +| (yck) | only matches strings the same as 'yck' | +| \| | matches any character before and after \| | +| \ | escape character | +| * | matches the preceding expression 0 or more times | +| + | matches the preceding expression 1 or more times | +| ? | the character before '?' is optional | + +## Flags + +| Flag | Effect | +| :------: | :--------------: | +| i | case-insensitive search | +| g | matches globally | +| m | multiline | + +## Character Shorthands + +| shorthand | Effect | +| :--: | :------------------------: | +| \w | alphanumeric characters, underline character | +| \W | the opposite of the above | +| \s | any blank character | +| \S | the opposite of the above | +| \d | numbers | +| \D | the opposite of the above | +| \b | start or end of a word | +| \B | the opposite of the above | +