diff --git a/Algorithm/algorithm-ch.md b/Algorithm/algorithm-ch.md index 14526e6f..4aea4602 100644 --- a/Algorithm/algorithm-ch.md +++ b/Algorithm/algorithm-ch.md @@ -102,8 +102,8 @@ ```js 8 ^ 7 // -> 15 8 ^ 8 // -> 0 -// 1000 & 0111 -> 1111 -> 15 -// 1000 & 1000 -> 0000 -> 0 +// 1000 ^ 0111 -> 1111 -> 15 +// 1000 ^ 1000 -> 0000 -> 0 ``` 从以上代码中可以发现按位异或就是不进位加法 diff --git a/Algorithm/algorithm-en.md b/Algorithm/algorithm-en.md index f9e21662..b76051ea 100644 --- a/Algorithm/algorithm-en.md +++ b/Algorithm/algorithm-en.md @@ -31,7 +31,7 @@ Shift arithmetic left is to move all the binary to the left, `10` is represented 10 >> 1 // -> 5 ``` -The right shift of the arithmetic is to move all the binary to the right and remove the extra right. `10` is represented as `1010` in binary, and becomes `101` after shifting one bit to the right, and converted to decimal is 5, so the right shift is seen as the following formula `a >> b => a / (2 ^ b)` by basically. +The bitwise right shift moves all the binary digits to the right and remove the extra left digit. `10` is represented as `1010` in binary, and becomes `101` after shifting one bit to the right, and becomes 5 in decimal value, so the right shift is basically the following formula: `a >> b => a / (2 ^ b)`. Right shift is very useful, for example, you can calculate the intermediate value in the binary algorithm. @@ -66,8 +66,8 @@ Each bit is different, and the result is 1 ```js 8 ^ 7 // -> 15 8 ^ 8 // -> 0 -// 1000 & 0111 -> 1111 -> 15 -// 1000 & 1000 -> 0000 -> 0 +// 1000 ^ 0111 -> 1111 -> 15 +// 1000 ^ 1000 -> 0000 -> 0 ``` From the above code, we can find that the bitwise XOR is the not carry addition. @@ -863,4 +863,4 @@ In the string correlation algorithm, Trie tree can solve many problems, and has - Word frequency statistics - Prefix matching -If you don't know much about the Trie tree, you can go [here](../DataStruct/dataStruct-zh.md#trie) to read \ No newline at end of file +If you don't know much about the Trie tree, you can go [here](../DataStruct/dataStruct-zh.md#trie) to read diff --git a/Browser/browser-ch.md b/Browser/browser-ch.md index cf89d610..6801d2d1 100644 --- a/Browser/browser-ch.md +++ b/Browser/browser-ch.md @@ -36,9 +36,9 @@ 事件触发有三个阶段 -- `document` 往事件触发处传播,遇到注册的捕获事件会触发 +- `window` 往事件触发处传播,遇到注册的捕获事件会触发 - 传播到事件触发处时触发注册的事件 -- 从事件触发处往 `document` 传播,遇到注册的冒泡事件会触发 +- 从事件触发处往 `window` 传播,遇到注册的冒泡事件会触发 事件触发一般来说会按照上面的顺序进行,但是也有特例,如果给一个目标节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行。 diff --git a/Framework/framework-en.md b/Framework/framework-en.md index 6d8693b2..ade948ca 100644 --- a/Framework/framework-en.md +++ b/Framework/framework-en.md @@ -294,12 +294,12 @@ ul.childNodes[2].remove() let fromNode = ul.childNodes[4] let toNode = node.childNodes[3] let cloneFromNode = fromNode.cloneNode(true) -let cloenToNode = toNode.cloneNode(true) +let cloneToNode = toNode.cloneNode(true) ul.replaceChild(cloneFromNode, toNode) -ul.replaceChild(cloenToNode, fromNode) +ul.replaceChild(cloneToNode, 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. +Of course, in actual operations, we need an identifier 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. @@ -393,7 +393,7 @@ We then have two steps of the algorithm. 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. +1. new node's `tagName` or `key` is different from that of the old one. This means 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. @@ -403,10 +403,10 @@ import Element from './element' export default function diff(oldDomTree, newDomTree) { // for recording changes - let pathchs = {} + let patches = {} // the index starts at 0 - dfs(oldDomTree, newDomTree, 0, pathchs) - return pathchs + dfs(oldDomTree, newDomTree, 0, patches) + return patches } function dfs(oldNode, newNode, index, patches) { @@ -450,7 +450,7 @@ We also have three steps for checking for property changes function diffProps(oldProps, newProps) { // three steps for checking for props // iterate oldProps for removed properties - // iterate newProps for chagned property values + // iterate newProps for changed property values // lastly check if new properties are added let change = [] for (const key in oldProps) { @@ -498,7 +498,7 @@ function listDiff(oldList, newList, index, patches) { let newKeys = getKeys(newList) let changes = [] - // for saving the node daa after changes + // for saving the node data 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 @@ -589,7 +589,7 @@ For this function, there are two main functionalities. 1. checking differences between two lists 2. marking nodes -In general, the functionalities impelemented are simple. +In general, the functionalities implemented are simple. ```js function diffChildren(oldChild, newChild, index, patches) { @@ -635,12 +635,12 @@ 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] +export default function patch(node, patches) { + let changes = patches[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]) { + if (changes && changes.length && patches[index]) { changeDom(node, changes) } let last = null @@ -648,7 +648,7 @@ export default function patch(node, patchs) { childNodes.forEach((item, i) => { index = last && last.children ? index + last.children.length + 1 : index + 1 - patch(item, patchs) + patch(item, patches) last = item }) } @@ -688,9 +688,9 @@ function changeDom(node, changes, noChild) { let fromNode = node.childNodes[change.from] let toNode = node.childNodes[change.to] let cloneFromNode = fromNode.cloneNode(true) - let cloenToNode = toNode.cloneNode(true) + let cloneToNode = toNode.cloneNode(true) node.replaceChild(cloneFromNode, toNode) - node.replaceChild(cloenToNode, fromNode) + node.replaceChild(cloneToNode, fromNode) break default: break @@ -717,14 +717,14 @@ let test2 = new Element('div', { id: '11' }, [test5, test4]) let root = test1.render() -let pathchs = diff(test1, test2) -console.log(pathchs) +let patches = diff(test1, test2) +console.log(patches) setTimeout(() => { console.log('start updating') - patch(root, pathchs) + patch(root, patches) 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 +Although the current implementation is simple, it's definitely enough for understanding Virtual Dom algorithms. diff --git a/JS/JS-br.md b/JS/JS-br.md index a1510762..877fd3d1 100644 --- a/JS/JS-br.md +++ b/JS/JS-br.md @@ -603,6 +603,7 @@ Quando lidando com uma função ou `undefined`, o objeto pode não ser serializa ```js let a = { age: undefined, + sex: Symbol('male'), jobs: function() {}, name: 'yck' } @@ -627,9 +628,12 @@ function structuralClone(obj) { var obj = {a: 1, b: { c: b }} + // preste atenção que esse método é assíncrono // ele consegue manipular `undefined` e referência circular do objeto -const clone = await structuralClone(obj); +(async () => { + const clone = await structuralClone(obj) +})() ``` # Modularização diff --git a/JS/JS-ch.md b/JS/JS-ch.md index a14dc4df..e2ac2e23 100644 --- a/JS/JS-ch.md +++ b/JS/JS-ch.md @@ -444,19 +444,19 @@ var foo = 1 }()) // -> ƒ foo() { foo = 10 ; console.log(foo) } ``` -因为当 JS 解释器在遇到非匿名的立即执行函数时,会创建一个辅助的特定对象,然后将函数名称作为这个对象的属性,因此函数内部才可以访问到 `foo`,但是这又个值是只读的,所以对它的赋值并不生效,所以打印的结果还是这个函数,并且外部的值也没有发生更改。 - -> ```js -> specialObject = {}; -> -> Scope = specialObject + Scope; -> -> foo = new FunctionExpression; -> foo.[[Scope]] = Scope; -> specialObject.foo = foo; // {DontDelete}, {ReadOnly} -> -> delete Scope[0]; // remove specialObject from the front of scope chain -> ``` +因为当 JS 解释器在遇到非匿名的立即执行函数时,会创建一个辅助的特定对象,然后将函数名称作为这个对象的属性,因此函数内部才可以访问到 `foo`,但是这个值又是只读的,所以对它的赋值并不生效,所以打印的结果还是这个函数,并且外部的值也没有发生更改。 + + ```js +specialObject = {}; + +Scope = specialObject + Scope; + +foo = new FunctionExpression; +foo.[[Scope]] = Scope; +specialObject.foo = foo; // {DontDelete}, {ReadOnly} + +delete Scope[0]; // remove specialObject from the front of scope chain + ``` # 闭包 @@ -481,7 +481,7 @@ for ( var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); -}˝ +} ``` 首先因为 `setTimeout` 是个异步函数,所有会先把循环全部执行完毕,这时候 `i` 就是 6 了,所以会输出一堆 6。 @@ -526,7 +526,7 @@ for ( let i=1; i<=5; i++) { { let ii = i setTimeout( function timer() { - console.log( i ); + console.log( ii ); }, i*1000 ); } i++ @@ -615,6 +615,7 @@ console.log(b.jobs.first) // FE 但是该方法也是有局限性的: - 会忽略 `undefined` +- 会忽略 `symbol` - 不能序列化函数 - 不能解决循环引用的对象 @@ -639,11 +640,12 @@ console.log(newObj) ![](https://user-gold-cdn.xitu.io/2018/3/28/1626b1ec2d3f9e41?w=840&h=100&f=png&s=30123) -在遇到函数或者 `undefined` 的时候,该对象也不能正常的序列化 +在遇到函数、 `undefined` 或者 `symbol` 的时候,该对象也不能正常的序列化 ```js let a = { age: undefined, + sex: Symbol('male'), jobs: function() {}, name: 'yck' } @@ -671,7 +673,9 @@ var obj = {a: 1, b: { }} // 注意该方法是异步的 // 可以处理 undefined 和循环引用对象 -const clone = await structuralClone(obj); +(async () => { + const clone = await structuralClone(obj) +})() ``` # 模块化 @@ -735,7 +739,7 @@ var load = function (module) { 对于 `CommonJS` 和 ES6 中的模块化的两者区别是: - 前者支持动态导入,也就是 `require(${path}/xx.js)`,后者目前不支持,但是已有提案 -- 前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用导入会对渲染有很大影响 +- 前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响 - 前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是后者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化 - 后者会编译成 `require/exports` 来执行的 @@ -762,60 +766,92 @@ define(function(require, exports, module) { 你是否在日常开发中遇到一个问题,在滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作。 -这些需求都可以通过函数防抖动来实现。尤其是第一个需求,如果在频繁的事件回调中做复杂计算,很有可能导致页面卡顿,不如将多次计算合并为一次计算,只在一个精确点做操作。因为防抖动的轮子很多,这里也不重新自己造个轮子了,直接使用 underscore 的源码来解释防抖动。 +这些需求都可以通过函数防抖动来实现。尤其是第一个需求,如果在频繁的事件回调中做复杂计算,很有可能导致页面卡顿,不如将多次计算合并为一次计算,只在一个精确点做操作。 + +PS:防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的 情况会每隔一定时间(参数wait)调用函数。 + +我们先来看一个袖珍版的防抖理解一下防抖的实现: ```js +// func是用户传入需要防抖的函数 +// wait是等待时间 +const debounce = (func, wait = 50) => { + // 缓存一个定时器id + let timer = 0 + // 这里返回的函数是每次用户实际调用的防抖函数 + // 如果已经设定过定时器了就清空上一次的定时器 + // 开始一个新的定时器,延迟执行用户传入的方法 + return function(...args) { + if (timer) clearTimeout(timer) + timer = setTimeout(() => { + func.apply(this, args) + }, wait) + } +} +// 不难看出如果用户调用该函数的间隔小于wait的情况下,上一次的时间还未到就被清除了,并不会执行函数 +``` +这是一个简单版的防抖,但是有缺陷,这个防抖只能在最后调用。一般的防抖会有immediate选项,表示是否立即调用。这两者的区别,举个栗子来说: +- 例如在搜索引擎搜索问题的时候,我们当然是希望用户输入完最后一个字才调用查询接口,这个时候适用`延迟执行`的防抖函数,它总是在一连串(间隔小于wait的)函数触发之后调用。 +- 例如用户给interviewMap点star的时候,我们希望用户点第一下的时候就去调用接口,并且成功之后改变star按钮的样子,用户就可以立马得到反馈是否star成功了,这个情况适用`立即执行`的防抖函数,它总是在第一次调用,并且下一次调用必须与前一次调用的时间间隔大于wait才会触发。 + +下面我们来实现一个带有立即执行选项的防抖函数 + + +```js +// 这个是用来获取当前时间戳的 +function now() { + return +new Date() +} /** - * underscore 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行 + * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行 * * @param {function} func 回调函数 * @param {number} wait 表示时间窗口的间隔 * @param {boolean} immediate 设置为ture时,是否立即调用函数 * @return {function} 返回客户调用函数 */ -_.debounce = function(func, wait, immediate) { - var timeout, args, context, timestamp, result; - - var later = function() { - // 现在和上一次时间戳比较 - var last = _.now() - timestamp; - // 如果当前间隔时间少于设定时间且大于0就重新设置定时器 - if (last < wait && last >= 0) { - timeout = setTimeout(later, wait - last); +function debounce (func, wait = 50, immediate = true) { + let timer, context, args + + // 延迟执行函数 + const later = () => setTimeout(() => { + // 延迟函数执行完毕,清空缓存的定时器序号 + timer = null + // 延迟执行的情况下,函数会在延迟函数中执行 + // 使用到之前缓存的参数和上下文 + if (!immediate) { + func.apply(context, args) + context = args = null + } + }, wait) + + // 这里返回的函数是每次实际调用的函数 + return function(...params) { + // 如果没有创建延迟执行函数(later),就创建一个 + if (!timer) { + timer = later() + // 如果是立即执行,调用函数 + // 否则缓存参数和调用上下文 + if (immediate) { + func.apply(this, params) } else { - // 否则的话就是时间到了执行回调函数 - timeout = null; - if (!immediate) { - result = func.apply(context, args); - if (!timeout) context = args = null; - } + context = this + args = params } - }; - - return function() { - context = this; - args = arguments; - // 获得时间戳 - timestamp = _.now(); - // 如果定时器不存在且立即执行函数 - var callNow = immediate && !timeout; - // 如果定时器不存在就创建一个 - if (!timeout) timeout = setTimeout(later, wait); - if (callNow) { - // 如果需要立即执行函数的话 通过 apply 执行 - result = func.apply(context, args); - context = args = null; - } - - return result; - }; - }; + // 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个 + // 这样做延迟函数会重新计时 + } else { + clearTimeout(timer) + timer = later() + } + } +} ``` 整体函数实现的不难,总结一下。 -- 对于按钮防点击来说的实现:一旦我开始一个定时器,只要我定时器还在,不管你怎么点击都不会执行回调函数。一旦定时器结束并设置为 `null`,就可以再次点击了。 -- 对于延时执行函数来说的实现:每次调用防抖动函数都会判断本次调用和之前的时间间隔,如果小于需要的时间间隔,就会重新创建一个定时器,并且定时器的延时为设定时间减去之前的时间间隔。一旦时间到了,就会执行相应的回调函数。 +- 对于按钮防点击来说的实现:如果函数是立即执行的,就立即调用,如果函数是延迟执行的,就缓存上下文和参数,放到延迟函数中去执行。一旦我开始一个定时器,只要我定时器还在,你每次点击我都重新计时。一旦你点累了,定时器时间到,定时器重置为 `null`,就可以再次点击了。 +- 对于延时执行函数来说的实现:清除定时器ID,如果是延迟调用就调用函数 # 节流 @@ -1394,7 +1430,7 @@ console.log('1', a) // -> '1' 1 对于以上代码你可能会有疑惑,这里说明下原理 - 首先函数 `b` 先执行,在执行到 `await 10` 之前变量 `a` 还是 0,因为在 `await` 内部实现了 `generators` ,`generators` 会保留堆栈中东西,所以这时候 `a = 0` 被保存了下来 -- 因为 `await` 是异步操作,所以会先执行 `console.log('1', a)` +- 因为 `await` 是异步操作,遇到`await`就会立即返回一个`pending`状态的`Promise`对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行,所以会先执行 `console.log('1', a)` - 这时候同步代码执行完毕,开始执行异步代码,将保存下来的值拿出来使用,这时候 `a = 10` - 然后后面就是常规执行代码了 diff --git a/JS/JS-en.md b/JS/JS-en.md index 6084ee29..e0191761 100644 --- a/JS/JS-en.md +++ b/JS/JS-en.md @@ -245,7 +245,7 @@ As for the latter, it first executes `new Foo()` to create an instance, then fin # This -`This`, a concept that is confusing to many peole, is actually not difficult to understand as long as you remember the following rules: +`This`, a concept that is confusing to many people, is actually not difficult to understand as long as you remember the following rules: ```js function foo() { @@ -577,6 +577,7 @@ console.log(b.jobs.first) // FE But this method also has its limits: * ignore `undefined` +* ignore `symbol` * unable to serialize function * unable to resolve circular references in an object ```js @@ -600,10 +601,11 @@ If an object is circularly referenced like the above example, you’ll find the ![](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. +When dealing with function, `undefined` or `symbol`, the object can also not be serialized properly. ```js let a = { age: undefined, + sex: Symbol('male'), jobs: function() {}, name: 'yck' } @@ -630,7 +632,9 @@ var obj = {a: 1, b: { }} // pay attention that this method is asynchronous // it can handle `undefined` and circular reference object -const clone = await structuralClone(obj); +(async () => { + const clone = await structuralClone(obj) +})() ``` # Modularization @@ -694,7 +698,7 @@ Let's then talk about `module.exports` and `exports`, which have similar usage, 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 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 synchronous 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`. @@ -1105,7 +1109,9 @@ _.debounce = function(func, wait, immediate) { // 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); + // else clear the current timer and reset a new timer + if (timeout) clearTimeout(timeout); + timeout = setTimeout(later, wait); if (callNow) { // if the immediate execution is needed, use apply to start the function result = func.apply(context, args); @@ -1289,7 +1295,7 @@ 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. +- Because `await` is an asynchronous operation, it will immediately return a `pending` state `Promise` object when it encounter `await`, and temporarily returning control of the execution code, so that the code outside the function can continue to be executed. `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 diff --git a/MP/mp-ch.md b/MP/mp-ch.md new file mode 100644 index 00000000..84d97abd --- /dev/null +++ b/MP/mp-ch.md @@ -0,0 +1,2408 @@ + + +- [小程序-登录](#小程序-登录) + - [unionid和openid](#unionid和openid) + - [关键Api](#关键api) + - [登录流程设计](#登录流程设计) + - [利用现有登录体系](#利用现有登录体系) + - [利用OpenId 创建用户体系](#利用openid-创建用户体系) + - [利用 Unionid 创建用户体系](#利用-unionid-创建用户体系) + - [注意事项](#注意事项) +- [小程序-图片导出](#小程序-图片导出) + - [基本原理](#基本原理) + - [如何优雅实现](#如何优雅实现) + - [注意事项](#注意事项-1) +- [小程序-数据统计](#小程序-数据统计) + - [设计一个埋点sdk](#设计一个埋点sdk) + - [分析接口](#分析接口) + - [微信自定义数据分析](#分析接口) +- [小程序-工程化](#小程序-工程化) + - [工程化做什么](#工程化做什么) + - [方案选型](#方案选型) + - [具体开发思路](#具体开发思路) +- [小程序-持续集成](#小程序-持续集成) + - [规范化的开发流程](#规范化的开发流程) + - [如何做小程序的持续集成](#如何做小程序的持续集成) + - [准备工作](#准备工作) + - [开发小程序的集成脚本](#开发小程序的集成脚本可以使用各种语言shell-js-python) + - [集成](#集成) + - [总结](#总结) +- [小程序架构](#小程序架构) + - [下载小程序完整包](#下载小程序完整包) + - [App Service - Life Cylce](#app-service---life-cylce) + - [面试题](#面试题) +- [View - WXML](#view---wxml) +- [View - WXSS](#view---wxss) + - [支持大部分CSS特性](#支持大部分css特性) + - [尺寸单位 rpx](#尺寸单位-rpx) + - [样式导入](#样式导入) + - [内联样式](#内联样式) + - [全局样式与局部样式](#全局样式与局部样式) + - [iconfont](#iconfont) +- [View - Component](#view---component) +- [View - Native Component](#view---native-component) +- [目前小程序的问题或限制](#目前小程序的问题或限制) + - [小程序HTTP2支持情况](#小程序http2支持情况) + - [HTTP2支持情况:模拟器与真机均不支持](#http2支持情况模拟器与真机均不支持) + - [HTTP2服务器需要对小程序做兼容性适配](#http2服务器需要对小程序做兼容性适配) +- [授权获取用户信息流程](#授权获取用户信息流程) +- [性能优化](#性能优化) + - [加载优化](#加载优化) + - [使用分包加载优化](#使用分包加载优化) + - [渲染性能优化](#渲染性能优化) +- [官方小程序技术能力规划](#官方小程序技术能力规划) + - [自定义组件2.0](#自定义组件20) + - [npm支持](#npm支持) + - [官方自定义组件](#官方自定义组件) + - [添加体验评分](#添加体验评分) + - [原生组件同层渲染](#原生组件同层渲染) +- [wepy vs mpvue](#wepy-vs-mpvue) + - [数据流管理](#数据流管理) + - [组件化](#组件化) + - [工程化](#工程化) + - [综合比较](#综合比较) + - [选型的个人看法](#选型的个人看法) +- [mpvue](#mpvue) + - [框架原理](#框架原理) + - [mpvue-loader](#mpvue-loader) + - [compiler](#compiler) + - [runtime](#runtime) + - [Class和Style为什么暂不支持组件](#class和style为什么暂不支持组件) + - [分包加载](#分包加载) + - [问题与展望](#问题与展望) +- [小程序-学习](#小程序-学习) + - [学习建议](#学习建议) + - [如何解决遇到的问题](#如何解决遇到的问题) + - [总结](#总结-1) +- [参考链接](#参考链接) + + + +# 小程序-登录 + +## unionid和openid + +了解小程序登录之前,我们写了解下小程序/公众号登录涉及到两个最关键的用户标识: + +- `OpenId` 是一个用户对于一个小程序/公众号的标识,开发者可以通过这个标识识别出用户。 +- `UnionId` 是一个用户对于同主体微信小程序/公众号/APP的标识,开发者需要在微信开放平台下绑定相同账号的主体。开发者可通过UnionId,实现多个小程序、公众号、甚至APP 之间的数据互通了。 + +## 关键Api + +- [`wx.login`](https://developers.weixin.qq.com/miniprogram/dev/api/api-login.html) 官方提供的登录能力 + +- [`wx.checkSession`](https://developers.weixin.qq.com/miniprogram/dev/api/signature.html#wxchecksessionobject) 校验用户当前的session_key是否有效 + +- [`wx.authorize`](https://developers.weixin.qq.com/miniprogram/dev/api/authorize.html) 提前向用户发起授权请求 + +- [`wx.getUserInfo`](https://developers.weixin.qq.com/miniprogram/dev/api/api-login.html) 获取用户基本信息 + + +## 登录流程设计 + + 以下从笔者接触过的几种登录流程来做阐述: + +### 利用现有登录体系 + + 直接复用现有系统的登录体系,只需要在小程序端设计用户名,密码/验证码输入页面,便可以简便的实现登录,只需要保持良好的用户体验即可。 + +### 利用OpenId 创建用户体系 + +👆提过,`OpenId` 是一个小程序对于一个用户的标识,利用这一点我们可以轻松的实现一套基于小程序的用户体系,值得一提的是这种用户体系对用户的打扰最低,可以实现静默登录。具体步骤如下: + + 1. 小程序客户端通过 `wx.login` 获取 code + + 2. 传递 code 向服务端,服务端拿到 code 调用微信登录凭证校验接口,微信服务器返回 `openid` 和会话密钥 `session_key` ,此时开发者服务端便可以利用 `openid` 生成用户入库,再向小程序客户端返回自定义登录态 + + 3. 小程序客户端缓存 (通过`storage`)自定义登录态(token),后续调用接口时携带该登录态作为用户身份标识即可 + +### 利用 Unionid 创建用户体系 + +如果想实现多个小程序,公众号,已有登录系统的数据互通,可以通过获取到用户 unionid 的方式建立用户体系。因为 unionid 在同一开放平台下的所所有应用都是相同的,通过 `unionid` 建立的用户体系即可实现全平台数据的互通,更方便的接入原有的功能,那如何获取 `unionid` 呢,有以下两种方式: + + 1. 如果户关注了某个相同主体公众号,或曾经在某个相同主体App、公众号上进行过微信登录授权,通过 `wx.login` 可以直接获取 到 `unionid` + + 2. 结合 `wx.getUserInfo` 和 `