- 小程序实现大转盘,九宫格抽奖,带跑马灯效果
基本实现功能 1,小程序仿天猫超市大转盘 2,九宫格转盘抽奖 3,积分抽奖 4,抽到的积分随机生成 5,抽奖结果可以同步到服务器(小程序云开发后台) 老规矩先看效果图 [图片] 简单说一下实现原理. 我们借助js的定时器,来执行一个加法。比如我们设置一个上限300,每过一定时间执行一次,然后我们再做一个随机数,这个随机数不停的++,直到总数大于300.就代表抽奖结束。核心代码如下。 [代码] //开始抽奖 startGame: function() { if (this.data.isRunning) return this.setData({ isRunning: true }) var _this = this; var indexSelect = 0 var i = 0; var timer = setInterval(function() { indexSelect++; let randomNum = Math.floor(Math.random() * 10) * 10; //可均衡获取0到90的随机整数 i += randomNum; if (i > 300) { //去除循环 clearInterval(timer) //获奖提示 let jifen = 1; let selectNum = _this.data.indexSelect console.log("选号:" + selectNum ); if (selectNum===0) { jifen = 2; } else if (selectNum === 1) { jifen = 3; } else if (selectNum === 2) { jifen = 4; } else if (selectNum === 3) { jifen = 5; } else if(selectNum === 4) { jifen = 6; } else if(selectNum === 5) { jifen = 8; } else if (selectNum === 6) { jifen = 10; } wx.showModal({ title: '恭喜您', content: '获得了' + jifen + "积分", showCancel: false, //去掉取消按钮 success: function(res) { if (res.confirm) { _this.setData({ isRunning: false }) } } }) } indexSelect = indexSelect % 8; _this.setData({ indexSelect: indexSelect }) }, (200 + i)) } [代码] 完整源码可以加我微信,如果有关于小程序的问题,可以加我微信2501902696(备注小程序)
2019-03-05 - 深入 CommonJs 与 ES6 Module
目前主流的模块规范 UMD CommonJs es6 module umd 模块(通用模块) [代码](function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.libName = factory()); }(this, (function () { 'use strict';}))); [代码] 如果你在[代码]js[代码]文件头部看到这样的代码,那么这个文件使用的就是 [代码]UMD[代码] 规范 实际上就是 amd + commonjs + 全局变量 这三种风格的结合 这段代码就是对当前运行环境的判断,如果是 [代码]Node[代码] 环境 就是使用 [代码]CommonJs[代码] 规范, 如果不是就判断是否为 [代码]AMD[代码] 环境, 最后导出全局变量 有了 [代码]UMD[代码] 后我们的代码和同时运行在 [代码]Node[代码] 和 [代码]浏览器上[代码] 所以现在前端大多数的库最后打包都使用的是 [代码]UMD[代码] 规范 CommonJs [代码]Nodejs[代码] 环境所使用的模块系统就是基于[代码]CommonJs[代码]规范实现的,我们现在所说的[代码]CommonJs[代码]规范也大多是指[代码]Node[代码]的模块系统 模块导出 关键字:[代码]module.exports[代码] [代码]exports[代码] [代码]// foo.js //一个一个 导出 module.exports.age = 1 module.exports.foo = function(){} exports.a = 'hello' //整体导出 module.exports = { age: 1, a: 'hello', foo:function(){} } //整体导出不能用`exports` 用exports不能在导入的时候使用 exports = { age: 1, a: 'hello', foo:function(){} } [代码] 这里需要注意 [代码]exports[代码] 不能被赋值,可以理解为在模块开始前[代码]exports = module.exports[代码], 因为赋值之后[代码]exports[代码]失去了 对[代码]module.exports[代码]的引用,成为了一个模块内的局部变量 模块导入 关键字:[代码]require[代码] [代码]const foo = require('./foo.js') console.log(foo.age) //1 [代码] 模块导入规则: 假设以下目录为 [代码]src/app/index.js[代码] 的文件 调用 [代码]require()[代码] [代码]./moduleA[代码] 相对路径开头 在没有指定后缀名的情况下 先去寻找同级目录同级目录:[代码]src/app/[代码] [代码]src/app/moduleA[代码] 无后缀名文件 按照[代码]javascript[代码]解析 [代码]src/app/moduleA.js[代码] js文件 按照[代码]javascript[代码]解析 [代码]src/app/moduleA.json[代码] json文件 按照[代码]json[代码]解析 [代码]src/app/moduleA.node[代码] node文件 按照加载的编译插件模块dlopen 同级目录没有 [代码]moduleA[代码] 文件会去找同级的 [代码]moduleA[代码]目录:[代码]src/app/moduleA[代码] [代码]src/app/moduleA/package.json[代码] 判断该目录是否有[代码]package.json[代码]文件, 如果有 找到[代码]main[代码]字段定义的文件返回, 如果 [代码]main[代码] 字段指向文件不存在 或 [代码]main[代码]字段不存在 或 [代码]package.json[代码]文件不存在向下执行 [代码]src/app/moduleA/index.js[代码] [代码]src/app/moduleA/index.json[代码] [代码]src/app/moduleA/index.node[代码] 结束 [代码]/module/moduleA[代码] 绝对路径开头 直接在[代码]/module/moduleA[代码]目录中寻找 规则同上 [代码]react[代码] 没有路径开头 没有路径开头则视为导入一个包 会先判断[代码]moduleA[代码]是否是一个核心模块 如[代码]path[代码],[代码]http[代码],优先导入核心模块 不是核心模块 会从当前文件的同级目录的[代码]node_modules[代码]寻找 [代码]/src/app/node_modules/[代码] 寻找规则同上 以导入[代码]react[代码]为例 [代码]先 node_modules 下 react 文件 -> react.js -> react.json -> react.node ->react目录 -> react package.json main -> index.js -> index.json -> index.node[代码] 如果没找到 继续向父目录的[代码]node_modules[代码]中找 [代码]/src/node_modules/[代码] [代码]/node_modules/[代码] 直到最后找不到 结束 [代码]require wrapper[代码] [代码]Node[代码]的模块 实际上可以理解为代码被包裹在一个[代码]函数包装器[代码]内 一个简单的[代码]require demo[代码]: [代码]function wrapper (script) { return '(function (exports, require, module, __filename, __dirname) {' + script + '\n})' } function require(id) { var cachedModule = Module._cache[id]; if(cachedModule){ return cachedModule.exports; } const module = { exports: {} } // 这里先将引用加入缓存 后面循环引用会说到 Module._cache[id] = module //当然不是eval这么简单 eval(wrapper('module.exports = "123"'))(module.exports, require, module, 'filename', 'dirname') return module.exports } [代码] 也可以查看:node module 源码 从以上代码我们可以知道: 模块只执行一次 之后调用获取的 [代码]module.exports[代码] 都是缓存哪怕这个 [代码]js[代码] 还没执行完毕(因为先加入缓存后执行模块) 模块导出就是[代码]return[代码]这个变量的其实跟[代码]a = b[代码]赋值一样, 基本类型导出的是值, 引用类型导出的是引用地址 [代码]exports[代码] 和 [代码]module.exports[代码] 持有相同引用,因为最后导出的是 [代码]module.exports[代码], 所以对[代码]exports[代码]进行赋值会导致[代码]exports[代码]操作的不再是[代码]module.exports[代码]的引用 循环引用 [代码]// a.js module.exports.a = 1 var b = require('./b') console.log(b) module.exports.a = 2 // b.js module.exports.b = 11 var a = require('./a') console.log(a) module.exports.b = 22 //main.js var a = require('./a') console.log(a) [代码] 运行此段代码结合上面的[代码]require demo[代码],分析每一步过程: [代码]执行 node main.js -> 第一行 require(a.js)[代码],([代码]node[代码] 执行也可以理解为调用了require方法,我们省略[代码]require(main.js)[代码]内容) [代码]进入 require(a)方法: 判断缓存(无) -> 初始化一个 module -> 将 module 加入缓存 -> 执行模块 a.js 内容[代码],(需要注意 是先加入缓存, 后执行模块内容) [代码]a.js: 第一行导出 a = 1 -> 第二行 require(b.js)[代码](a 只执行了第一行) [代码]进入 require(b) 内 同 1 -> 执行模块 b.js 内容[代码] [代码]b.js: 第一行 b = 11 -> 第二行 require(a.js)[代码] [代码]require(a) 此时 a.js 是第二次调用 require -> 判断缓存(有)-> cachedModule.exports -> 回到 b.js[代码](因为[代码]js[代码]对象引用问题 此时的 [代码]cachedModule.exports = { a: 1 }[代码]) [代码]b.js:第三行 输出 { a: 1 } -> 第四行 修改 b = 22 -> 执行完毕回到 a.js[代码] [代码]a.js:第二行 require 完毕 获取到 b -> 第三行 输出 { b: 22 } -> 第四行 导出 a = 2 -> 执行完毕回到 main.js[代码] [代码]main.js:获取 a -> 第二行 输出 { a: 2 } -> 执行完毕[代码] 以上就是[代码]node[代码]的[代码]module[代码]模块解析和运行的大致规则 es6 module [代码]ES6[代码] 之前 [代码]javascript[代码] 一直没有属于自己的模块规范,所以社区制定了 [代码]CommonJs[代码]规范, [代码]Node[代码] 从 [代码]Commonjs[代码] 规范中借鉴了思想于是有了 [代码]Node[代码] 的 [代码]module[代码],而 [代码]AMD 异步模块[代码] 也同样脱胎于 [代码]Commonjs[代码] 规范,之后有了运行在浏览器上的 [代码]require.js[代码] [代码]es6 module[代码] 基本语法: export [代码]export * from 'module'; //重定向导出 不包括 module内的default export { name1, name2, ..., nameN } from 'module'; // 重定向命名导出 export { import1 as name1, import2 as name2, ..., nameN } from 'module'; // 重定向重命名导出 export { name1, name2, …, nameN }; // 与之前声明的变量名绑定 命名导出 export { variable1 as name1, variable2 as name2, …, nameN }; // 重命名导出 export let name1 = 'name1'; // 声明命名导出 或者 var, const,function, function*, class export default expression; // 默认导出 export default function () { ... } // 或者 function*, class export default function name1() { ... } // 或者 function*, class export { name1 as default, ... }; // 重命名为默认导出 [代码] [代码]export[代码] 规则 [代码]export * from ''[代码] 或者 [代码]export {} from ''[代码],重定向导出,重定向的命名并不能在本模块使用,只是搭建一个桥梁,例如:这个[代码]a[代码]并不能在本模块内使用 [代码]export {}[代码], 与变量名绑定,命名导出 [代码]export Declaration[代码],声明的同时,命名导出, Declaration就是: [代码]var[代码], [代码]let[代码], [代码]const[代码], [代码]function[代码], [代码]function*[代码], [代码]class[代码] 这一类的声明语句 [代码]export default AssignmentExpression[代码],默认导出, AssignmentExpression的 范围很广,可以大致理解 为除了声明[代码]Declaration[代码](其实两者是有交叉的),[代码]a=2[代码],[代码]i++[代码],[代码]i/4[代码],[代码]a===b[代码],[代码]obj[name][代码],[代码]name in obj[代码],[代码]func()[代码],[代码]new P()[代码],[代码][1,2,3][代码],[代码]function(){}[代码]等等很多 import [代码]// 命名导出 module.js let a = 1,b = 2 export { a, b } export let c = 3 // 命名导入 main.js import { a, b, c } from 'module'; // a: 1 b: 2 c: 3 import { a as newA, b, c as newC } from 'module'; // newA: 1 b: 2 newC: 3 // 默认导出 module.js export default 1 // 默认导入 main.js import defaultExport from 'module'; // defaultExport: 1 // 混合导出 module.js let a = 1 export { a } const b = 2 export { b } export let c = 3 export default [1, 2, 3] // 混合导入 main.js import defaultExport, { a, b, c as newC} from 'module'; //defaultExport: [1, 2, 3] a: 1 b: 2 newC: 3 import defaultExport, * as name from 'module'; //defaultExport: [1, 2, 3] name: { a: 1, b: 2, c: 3 } import * as name from 'module'; // name: { a: 1, b: 2, c: 3, default: [1, 2, 3] } // module.js Array.prototype.remove = function(){} //副作用 只运行一个模块 import 'module'; // 执行module 不导出值 多次调用module.js只运行一次 //动态导入(异步导入) var promise = import('module'); [代码] [代码]import[代码] 规则 [代码]import { } from 'module'[代码], 导入[代码]module.js[代码]的命名导出 [代码]import defaultExport from 'module'[代码], 导入[代码]module.js[代码]的默认导出 [代码]import * as name from 'module'[代码], 将[代码]module.js的[代码]的所有导出合并为[代码]name[代码]的对象,[代码]key[代码]为导出的命名,默认导出的[代码]key[代码]为[代码]default[代码] [代码]import 'module'[代码],副作用,只是运行[代码]module[代码],不为了导出内容例如 polyfill,多次调用次语句只能执行一次 [代码]import('module')[代码],动态导入返回一个 [代码]Promise[代码],[代码]TC39[代码]的[代码]stage-3[代码]阶段被提出 tc39 import [代码]ES6 module[代码] 特点 [代码]ES6 module[代码]的语法是静态的 [代码]import[代码] 会自动提升到代码的顶层 [代码]export[代码] 和 [代码]import[代码] 只能出现在代码的顶层,下面这段语法是错误的 [代码] //if for while 等都无法使用 { export let a = 1 import defaultExport from 'module' } true || export let a = 1 [代码] [代码]import[代码] 的导入名不能为字符串或在判断语句,下面代码是错误的 [代码]import 'defaultExport' from 'module' let name = 'Export' import 'default' + name from 'module' [代码] 静态的语法意味着可以在编译时确定导入和导出,更加快速的查找依赖,可以使用[代码]lint[代码]工具对模块依赖进行检查,可以对导入导出加上类型信息进行静态的类型检查 ####[代码]ES6 module[代码]的导出是绑定的 #### 使用 [代码]import[代码] 被导入的模块运行在严格模式下 使用 [代码]import[代码] 被导入的变量是只读的,可以理解默认为 [代码]const[代码] 装饰,无法被赋值 使用 [代码]import[代码] 被导入的变量是与原变量绑定/引用的,可以理解为 [代码]import[代码] 导入的变量无论是否为基本类型都是引用传递 [代码]// js中 基础类型是值传递 let a = 1 let b = a b = 2 console.log(a,b) //1 2 // js中 引用类型是引用传递 let obj = {name:'obj'} let obj2 = obj obj2.name = 'obj2' console.log(obj.name, obj2.name) // obj2 obj2 // es6 module 中基本类型也按引用传递 // foo.js export let a = 1 export function count(){ a++ } // main.js import { a, count } from './foo' console.log(a) //1 count() console.log(a) //2 // export default 是无法 a 的动态绑定 这一点跟 CommonJs 有点相似 都是值的拷贝 let a = 1; export default a // 可以用另一种方式实现 default 的动态绑定 let a = 1; export { a as default } export function count(){ a++ } // 就跟上面 main.js 一样 [代码] 上面这段代码就是 [代码]CommonJs[代码] 导出变量 和 [代码]ES6[代码] 导出变量的区别 es module 循环引用 [代码]// bar.js import { foo } from './foo' console.log(foo); export let bar = 'bar' // foo.js import { bar } from './bar' console.log(bar); export let foo = 'foo' // main.js import { bar } from './bar' console.log(bar) [代码] [代码]执行 main.js -> 导入 bar.js[代码] [代码]bar.js -> 导入 foo.js[代码] [代码]foo.js -> 导入 bar.js -> bar.js 已经执行过直接返回 -> 输出 bar -> bar is not defined, bar 未定义报错[代码] 我们可以使用[代码]function[代码]的方式解决: [代码]// bar.js import { foo } from './foo' console.log(foo()); export function bar(){ return 'bar' } // foo.js import { bar } from './bar' console.log(bar()); export function foo(){ return 'foo' } // main.js import { bar } from './bar' console.log(bar) [代码] 因为函数声明会提示到文件顶部,所以就可以直接在 [代码]foo.js[代码] 调用还没执行完毕的[代码]bar.js[代码]的 [代码]bar[代码] 方法,不要在函数内使用外部变量,因为变量还未声明([代码]let,const[代码])和赋值,[代码]var[代码] CommonJs 和 ES6 Module 的区别 其实上面我们已经说到了一些区别 [代码]CommonJs[代码]导出的是变量的一份拷贝,[代码]ES6 Module[代码]导出的是变量的绑定([代码]export default[代码] 是特殊的) [代码]CommonJs[代码]是单个值导出,[代码]ES6 Module[代码]可以导出多个 [代码]CommonJs[代码]是动态语法可以写在判断里,[代码]ES6 Module[代码]静态语法只能写在顶层 [代码]CommonJs[代码]的 [代码]this[代码] 是当前模块,[代码]ES6 Module[代码]的 [代码]this[代码] 是 [代码]undefined[代码] 易混淆点 模块语法与解构 [代码]module语法[代码]与[代码]解构语法[代码]很容易混淆,例如: [代码]import { a } from 'module' const { a } = require('module') [代码] 尽管看上去很像,但是不是同一个东西,这是两种完全不一样的语法与作用,ps:两个人撞衫了,穿一样的衣服你不能说这俩人就是同一个人 [代码]module[代码] 的语法: 上面有写 [代码]import/export { a } / { a, b } / { a as c} FromClause[代码] [代码]解构[代码] 的语法: [代码]let { a } = { a: 1 } let { a = 2 } = { } let { a: b } = { a: 1 } let { a: b = 2, ...res } = { name:'a' } let { a: b, obj: { name } } = { a: 1, obj: { name: '1' } } function foo({a: []}) {} [代码] 他们是差别非常大的两个东西,一个是模块导入导出,一个是获取对象的语法糖 导出语法与对象属性简写 同样下面这段代码也容易混淆 [代码]let a = 1 export { a } // 导出语法 export default { a } // 属性简写 导出 { a: 1 } 对象 module.exports = { a } // 属性简写 导出 { a: 1 } 对象 [代码] [代码]export default[代码] 和 [代码]module.exports[代码] 是相似的 ES6 module 支持 CommonJs 情况 先简单说一下各个环境的 [代码]ES6 module[代码] 支持 [代码]CommonJs[代码] 情况,后面单独说如何在不同环境中使用 因为 [代码]module.exports[代码] 很像 [代码]export default[代码] 所以 [代码]ES6模块[代码] 可以很方便兼容 [代码]CommonJs[代码] 在[代码]ES6 module[代码]中使用[代码]CommonJs[代码]规范,根据各个环境,打包工具不同也是不一样的 我们现在大多使用的是 [代码]webpack[代码] 进行项目构建打包,因为现在前端开发环境都是在 [代码]Node[代码] 环境原因,而 [代码]npm[代码] 的包都是 [代码]CommonJs[代码] 规范的,所以 [代码]webpack[代码] 对[代码]ES6[代码]模块进行扩展 支持 [代码]CommonJs[代码],并支持[代码]node[代码]的导入[代码]npm[代码]包的规范 如果你使用 [代码]rollup[代码],想在[代码]ES Module[代码]中支持[代码]Commonjs[代码]规范就需要下载[代码]rollup-plugin-commonjs[代码]插件,想要导入[代码]node_modules[代码]下的包也需要[代码]rollup-plugin-node-resolve[代码]插件 如果你使用 [代码]node[代码],可以在 [代码].mjs[代码] 文件使用 [代码]ES6[代码],也支持 [代码]CommonJs[代码] 查看 nodejs es-modules.md 在浏览器环境 不支持[代码]CommonJs[代码] node 与 打包工具[代码]webpack,rollup[代码]的导入 [代码]CommonJs[代码] 差异 [代码]// module.js module.export.a = 1 // index.js webpack rollup import * as a from './module' console.log(a) // { a: 1, default: { a:1 } } // index.mjs node import * as a from './module' console.log(a) // { default: { a:1 } } [代码] [代码]node[代码] 只是把 [代码]module.exports[代码] 整体当做 [代码]export default[代码] 打包工具除了把 [代码]module.export[代码] 整体当做 [代码]export default[代码],还把 [代码]module.export[代码] 的每一项 又当做 [代码]export[代码] 输出,这样做是为了更加简洁 [代码]import defaultExport from './foo'[代码], [代码]defaultExport.foo()[代码] [代码]import { foo } from './foo'[代码], [代码]foo()[代码] 使用 ES6 Module 可以在 es6module example 仓库中获取代码在本地进行测试验证 浏览器中使用 你需要起一个[代码]Web服务器[代码]来访问,双击本地运行 [代码]index.html[代码] 并不会执行 [代码]type=module[代码] 标签 我们可以对 [代码]script[代码] 标签的 [代码]type[代码] 属性加上 [代码]module[代码] 先定义两个模块 [代码]// index.js import module from './module.js' console.log(module) // 123 // module.js export default 123 [代码] 在[代码]html[代码]中内联调用 [代码]<!-- index.html --> <script type="module"> import module from './module.js' console.log(module) // 123 </script> [代码] 在[代码]html[代码]中通过 [代码]script[代码] 的 [代码]src[代码] 引用 [代码]<!-- index.html --> <script type="module" src="index.js"></script> // 控制台 123 [代码] 浏览器导入路径规则 [代码]https://example.com/apples.mjs[代码] [代码]http://example.com/apples.js[代码] [代码]//example.com/bananas[代码] [代码]./strawberries.mjs.cgi[代码] [代码]../lychees[代码] [代码]/limes.jsx[代码] [代码]data:text/javascript,export default 'grapes';[代码] [代码]blob:https://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f[代码] 补充: 不加 后缀名 找不到具体的文件 后端可以修改接口[代码]/getjs?name=module[代码]这一类的,不过后端要返回 [代码]Content-Type: application/javascript[代码] 确保返回的是[代码]js[代码],因为浏览器是根据 [代码]MIME type[代码] 识别的 因为 [代码]ES6 Module[代码] 在浏览器中兼容并不是很好兼容性表,这里就不介绍浏览器支持情况了,我们一般不会直接在浏览器中使用 Nodejs中使用 nodejs es-modules.md 在 [代码]Node v8.5.0[代码] 以上支持 [代码]ES Module[代码],需要 [代码].mjs[代码]扩展名 NOTE: DRAFT status does not mean ESM will be implemented in Node core. Instead that this is the standard, should Node core decide to implement ESM. At which time this draft would be moved to ACCEPTED. (上面链接可以知道 [代码]ES Module[代码]的状态是 [代码]DRAFT[代码], 属于起草阶段) [代码]// module.mjs export default 123 // index.mjs import module from './module.mjs' console.log(module) // 123 [代码] 我们需要执行 [代码]node --experimental-modules index.mjs[代码] 来启动 会提示一个 [代码]ExperimentalWarning: The ESM module loader is experimental.[代码]该功能是实验性的(此提示不影响执行) [代码]ES Module[代码] 中导入 [代码]CommonJs[代码] [代码]// module.js module.exports.a = 123 // module.exports 就相当于 export default // index.mjs import module from './module.js' console.log(module) // { a: 123 } import * as module from './module.js' console.log(module) // { get default: { a: 123 } } import { default as module } from './module.js'; console.log(module) // { a: 123 } import module from 'module'; // 导入npm包 导入规则与 require 差不多 [代码] 导入路径规则与[代码]require[代码]差不多 这里要注意 [代码]module[代码] 扩展名为 [代码].js[代码],[代码].mjs[代码]专属于 [代码]es module[代码],[代码]import form[代码]导入的文件后缀名只能是[代码].mjs[代码],在 [代码].mjs[代码]中 [代码]module[代码]未定义, 所以调用 [代码]module.exports,exports[代码] 会报错 [代码]node[代码]中 [代码]CommonJs[代码] 导入 [代码]es module[代码] 只能使用 [代码]import()[代码] 动态导入/异步导入 [代码]// es.mjs let foo = {name: 'foo'}; export default foo; export let a = 1 // cjs import('./es').then((res)=>{ console.log(res) // { get default: {name: 'foo'}, a: 1 } }); [代码] webpack中使用 从 [代码]webpack2[代码] 就默认支持 [代码]es module[代码] 了,并默认支持 [代码]CommonJs[代码],支持导入 [代码]npm[代码]包, 这里 [代码]import[代码] 语法上面写太多 就不再写了 rollup中使用 [代码]rollup[代码] 专注于 [代码]es module[代码],可以将 [代码]es module[代码] 打包为主流的模块规范,注意这里与 [代码]webpack[代码] 的区别,我们可以在 [代码]webpack[代码] 的 [代码]js[代码] 中使用 [代码]Commonjs[代码] 语法, 但是 [代码]rollup[代码] 不支持,[代码]rollup[代码]需要 [代码]plugin[代码] 支持,包括加载 [代码]node_modules[代码] 下的包 [代码]form 'react'[代码] 也需要 [代码]plugin[代码] 支持 可以看到 [代码]es module[代码] 在[代码]浏览器[代码]与[代码]node[代码]中兼容性差与实验功能的 我们大多时候在 打包工具 中使用 Tree-shaking 在最后我们说一下经常跟 [代码]es module[代码] 一起出现的一个名词 [代码]Tree-shaking[代码] [代码]Tree-shaking[代码] 我们先直译一下 树木摇晃 就是 摇晃树木把上面枯死的树叶晃下来,在代码中就是把没有用到的代码删除 [代码]Tree-shaking[代码] 最早由 [代码]rollup[代码] 提出,之后 [代码]webpack 2[代码] 也开始支持 这都是基于 [代码]es module[代码] 模块特性的静态分析 rollup 下面代码使用 [代码]rollup[代码] 进行打包: [代码]// module.js export let foo = 'foo' export let bar = 'bar' // index.js import { foo } from './module' console.log(foo) // foo [代码] 在线运行 我们可以修改例子与导出多种规范 打包结果: [代码]let foo = 'foo'; console.log(foo); // foo [代码] 可以看到 [代码]rollup[代码] 打包结果非常的简洁,并去掉了没有用到的 [代码]bar[代码] 是否支持对导入 [代码]CommonJs[代码] 的规范进行 [代码]Tree-shaking[代码]: [代码]// index.js import { a } from './module' console.log(a) // 1 // module.js module.exports.a = 1 module.exports.b = 2 [代码] 打包为 [代码]es module[代码] [代码]var a_1 = 2; console.log(a_1); [代码] 可以看到去掉了未使用的 [代码]b[代码] webpack 我们下面看看 [代码]webpack[代码] 的支持情况 [代码]// src/module.js export function foo(){ return 'foo' } export function bar(){ return 'bar' } // src/index.js import { foo } from './module' console.log(foo()) [代码] 执行 [代码]npx webpack -p[代码](我们使用webpack 4,0配置,-p开启生成模式 自动压缩) 打包后我们在打包文件搜索 [代码]bar[代码] 没有搜到,[代码]bar[代码]被删除 我们将上面例子修改一下: [代码]// src/module.js module.exports.foo = function (){ return 'foo' } module.exports.bar = function (){ return 'bar' } // src/index.js import { foo } from './module' console.log(foo()) [代码] 打包后搜索 [代码]bar[代码] 发现[代码]bar[代码]存在,[代码]webpack[代码] 并不支持对[代码]CommonJs[代码] 进行 [代码]Tree-shaking[代码] pkg.module [代码]webpack[代码] 不支持 [代码]Commonjs[代码] [代码]Tree-shaking[代码],但现在[代码]npm[代码]的包都是[代码]CommonJs[代码]规范的,这该怎么办呢 ?如果我发了一个新包是 [代码]es module[代码] 规范, 但是如果代码运行在 [代码]node[代码] 环境,没有经过打包 就会报错 有一种按需加载的方案 全路径导入,导入具体的文件: [代码]// src/index.js import remove from 'lodash/remove' import add from 'lodash/add' console.log(remove(), add()) [代码] 使用一个还好,如果用多个的话会有很多 [代码]import[代码] 语句 还可以使用插件如 [代码]babel-plugin-lodash, & lodash-webpack-plugin[代码] 但我们不能发一个库就自己写插件 这时就提出了在 [代码]package.json[代码] 加一个 [代码]module[代码] 的字段来指向 [代码]es module[代码]规范的文件,[代码]main -> CommonJs[代码],那么[代码]module - es module[代码] pkg.module [代码]webpack[代码] 与 [代码]rollup[代码] 都支持 [代码]pkg.module[代码] 加了 [代码]module[代码] 字段 [代码]webpack[代码] 就可以识别我们的 [代码]es module[代码],但是还有一个问题就是 [代码]babel[代码] 我们一般使用 [代码]babel[代码] 都会排除 [代码]node_modules[代码],所以我们这个 [代码]pkg.module[代码] 只是的 [代码]es6 module[代码]必须是编译之后的 [代码]es5[代码] 代码,因为 [代码]babel[代码] 不会帮我们编译,我们的包就必须是 拥有 es6 module 规范的 es5 代码 如果你使用了 [代码]presets-env[代码] 因为会把我们的代码转为 [代码]CommonJs[代码] 所以就要设置 [代码]"presets": [["env", {"modules":false}][代码] 不将[代码]es module[代码] 转为 [代码]CommonJs[代码] [代码]webpack[代码] 与 [代码]rollup[代码] 的区别 [代码]webpack[代码] 不支持导出 [代码]es6 module[代码] 规范,[代码]rollup[代码] 支持导出 [代码]es6 module[代码] [代码]webpack[代码] 打包后代码很多冗余无法直接看,[代码]rollup[代码] 打包后的代码简洁,可读,像源码 [代码]webpack[代码] 可以进行代码分割,静态资源处理,[代码]HRM[代码],[代码]rollup[代码] 专注于 [代码]es module[代码],[代码]tree-shaking[代码]更加强大的,精简 如果是开发应用可以使用 [代码]webpack[代码],因为可以进行代码分割,静态资源,[代码]HRM[代码],插件 如果是开发类似 [代码]vue[代码],[代码]react[代码] 等类库,[代码]rollup[代码] 更好一些,因为可以使你的代码精简,无冗余代码,执行更快,导出多种模块语法 结语 本文章介绍了 [代码]Commonjs[代码] 和 [代码]ES6 Module[代码],导入导出的语法规则,路径解析规则,两者的区别,容易混淆的地方,在不同环境的区别,在不同环境的使用,[代码]Tree-shaking[代码],与 [代码]webpack[代码],[代码]rollup[代码] 的区别 希望您读完文章后,能对前端的模块化有更深的了解 参考链接 ECMAScript® 2015 Language Specification sec-imports/sec-exports MDN import github nodejs lib/module github nodejs node-eps/002-es-modules nodejs docs modules Understanding ECMAScript 6 ECMAScript 6 入门 es6-modules-final
2019-07-24 - 几行代码实现小程序云开发提现功能
先看效果: [图片] 纯云开发实现,下面说使用步骤: 一:开通商户的企业付款到领取功能 说明地址: https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1 使用条件 1、商户号(或同主体其他非服务商商户号)已入驻90日 2、截止今日回推30天,商户号(或同主体其他非服务商商户号)连续不间断保持有交易 使用条件是第一难,第二难在下面这里 [图片] 在网上找了很多,感觉是云开发这里的一个不完善地方,如果不填ip,会报这种错 [代码]{"errorCode":1,"errorMessage":"user code exception caught","stackTrace":"NO_AUTH"} [代码] [代码]<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[此IP地址不允许调用接口,如有需要请登录微信支付商户平台更改配置]]></return_msg> <mch_appid><![CDATA[wx383426ad9ffe1111]]></mch_appid> <mchid><![CDATA[1536511111]]></mchid> <result_code><![CDATA[FAIL]]></result_code> <err_code><![CDATA[NO_AUTH]]></err_code> <err_code_des><![CDATA[此IP地址不允许调用接口,如有需要请登录微信支付商户平台更改配置]]></err_code_des> </xml> [代码] 云开发没有ip这个概念,所以这里有些无从下手,不过这里我采用了个替代方案,参考了社区帖子: https://developers.weixin.qq.com/community/develop/doc/00088cff3a40d87d80f7267b65b800 之后我也亲自验证了,基本上就是这几个,当然肯定不够,但是可以自己在逻辑上进行处理,ip以下: [代码]172.81.207.12 172.81.212.74 172.81.236.99 172.81.235.12 172.81.245.51 212.64.65.131 212.64.84.22 212.64.85.35 212.64.85.139 212.64.87.134 [代码] 接着,可以动手了 二、云开发部分 1、设置云存储 证书配置地址: [图片] 下载后有三个文件,我们只需要p12结尾的那个 [图片] 然后,将这个apiclient_cert.p12文件上传到你的云存储 [图片] 这里处理完了,我们只需要一个东西,就是fileID也就是常说的云存储ID(上图红框内容) 2、配置云函数 新建云函数ref云函数 [图片] 代码如下: [代码]const config = { appid: 'wx383426ad9ffe1111', //小程序Appid envName: 'zf-shcud', // 小程序云开发环境ID mchid: '1111111111', //商户号 partnerKey: '1111111111111111111111', //此处填服务商密钥 pfx: '', //证书初始化 fileID: 'cloud://zf-shcud.11111111111111111/apiclient_cert.p12' //证书云存储id }; const cloud = require('wx-server-sdk') cloud.init({ env: config.envName }) const db = cloud.database(); const tenpay = require('tenpay'); //支付核心模块 exports.main = async(event, context) => { //首先获取证书文件 const res = await cloud.downloadFile({ fileID: config.fileID, }) config.pfx = res.fileContent let pay = new tenpay(config,true) let result = await pay.transfers({ //这部分参数含义参考https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2 partner_trade_no: 'bookreflect' + Date.now() + event.num, openid: event.userinfo._openid, check_name: 'NO_CHECK', amount: parseInt(event.num) * 100, desc: '二手书小程序提现', }); if (result.result_code == 'SUCCESS') { //如果提现成功后的操作 //以下是进行余额计算 let re=await db.collection('user').doc(event.userinfo._id).update({ data: { parse: event.userinfo.parse - parseInt(event.num) } }); return re } } [代码] 需安装的依赖:wx-server-sdk、tenpay 这里只是实现了简单原始的提现操作,关于提现后,比如防止重复提交,提现限额这些,在开源二手书商城上有完整流程,地址: https://github.com/xuhuai66/used-book-pro 这种办法,不是每次都能成功提现,小概率遇到ip未在白名单情况,还是希望,云开发团队能尽快出一个更好的解决方案吧
2019-09-21 - 只有三行代码的神奇云函数的功能之四:获取电话号码
这是一个神奇的网站,哦不,神奇的云函数,它只有三行代码:(真的只有三行哦) 云函数:login index.js: const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event) => { return { ...event, ...cloud.getWXContext() } } 神奇功能之四:获取电话号码: 还是这三行代码,获取用户的电话号码。 wxml: <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber" >{{mobile||"获得电话号码"}}</button> js: getPhoneNumber: function (e) { wx.cloud.callFunction({ name: 'login', data: {weRunData: wx.cloud.CloudID(e.detail.cloudID)} }).then(res => { this.setData({ mobile: res.result.weRunData.data.phoneNumber }) }) } 其他功能: 神奇功能之一:获取openid: https://developers.weixin.qq.com/community/develop/article/doc/00080c6e3746d8a940f9b43e55fc13 神奇功能之二:不用授权获取unionid: https://developers.weixin.qq.com/community/develop/article/doc/000a0c6b580338e947f9db0c65b813 神奇功能之三:100%成功获取unionid: https://developers.weixin.qq.com/community/develop/article/doc/00066a967c4e384949f93fe1151413 神奇功能之五:获取群id: 将小程序分享到某群里,可获得该群的群id, https://developers.weixin.qq.com/community/develop/article/doc/000ea802c00f70894cf9fe72556013 [图片]
2020-12-16 - 已解决。小程序获取手机号时,checkSession通过但是获取手机号解密失败
一开始我的处理方式是在页面直接用checkSession,我的session_key是在index.js登录的时候保存到storage,这里check回调的是“success”。 但是把此时storage里面的session_key结合授权按钮的参数去进行解密是失败的,需要在当前的Page再登陆一次才能成功。 不推荐把session_key存放在缓存。所以以上做法直接跳过。 最后参考了一个朋友的做法,在Page onLoad的时候执行一次wx.login(),然后拿到新的session_key,再用此时的新key去解密就通了。或者改为请求解密之前执行一次登录,据说出问题的概率还是很大 结尾补充:最后一种方法还有个问题要考虑,就是最好执行获取手机号之前再checkSession一下(尽管没啥用)。 问题源头,由于这个函数在校验session_key的时候,无论是过期的key还是新的key都是success,所以有了之后一些列的问题,session_key的状态没法把控 [代码]Page({ data: { currentSessionKey: null }, onLoad: function(options) { /* do something*/ const here = this; // 执行登录确保session_key在线 wx.login({ success(res) { if (res.code) { // call()是我自己基于wx.request封装的一个请求函数工具,这里通过后端发送登录请求获得openid const data = call(userLogin, { code: res.code }); data.then(obj => { if (!obj.error) { here.setData({ currentSessionKey: obj.result.session_key }) } }); } }, fail(error) { throw error; } }); }, // 点击按钮获取手机号权限并解析<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber" bindtap='doMyAction'>获取手机号</button> getPhoneNumber: function (e) { const { encryptedData, iv } = e.detail; const options = { encryptedData: encryptedData, iv: iv, sessionKey: this.data.currentSessionKey }; here.doGetPhone(options); }, doMyAction: function() { // 还可以做一些事情 }, doGetPhone: function (options) { const { sessionKey, encryptedData, iv } = options; const here = this; // 向服务器请求解密 wx.request({ // 这里是解密用的接口 url: 'https://xxx.com/python/decrypt', method: 'POST', data: { sessionKey: sessionKey, encryptedData: encryptedData, iv: iv }, success(res) { // 最终获取到用户数据,国家代号前缀、不带前缀的手机号。默认是不带前缀 const { countryCode, purePhoneNumber } = res.data; here.pageForward(countryCode, purePhoneNumber); }, fail(error) { console.log(error); here.pageForward(); } }) }, pageForward: function(countryCode, purePhoneNumber) { // 获取成功后我是跳转到另一个页面 wx.navigateTo({ url: `/pages/person/index?phone=${purePhoneNumber}` }) } }) [代码]
2020-09-15 - 小程序实现提现
只有小程序的appid,没有微信公众号的appid可以实现企业到零钱功能吗?
2019-04-02 - 关于scroll-view的bindscrolltolower多次触发的解决办法
在使用scroll-view的时候用bindscrolltolower都会遇到多次触发,上拉一次就触发多少。 下面是解决办法, 先在data里loadMore:true, 然后再loadMore function里直接set.Data({loadMore:false}) loadMore:function() { var that = this that.setData({ loadMore:false }) this.getList() } 然后再getList function里网络请求成功后 that.setData({ loadMore:true })然后view页面的bindscrolltolower使用bindscrolltolower=“{{loadMore ? 'loadMore' : ' '}}”这样就可以了。
2017-11-13 - 小程序组件化开发
一、组件实现方式:template模板和component构造器 除了component,小程序中还有另一种组件化你的方式template模板 区别: 1、template主要是展示,方法则需要在调用的页面中定义。简单来说,如果只是展示,使用template就足够了 2、而component组件则有自己的业务逻辑,可以看做一个独立的page页面。如果涉及到的业务逻辑交互比较多,那就最好使用component组件了。 二、template模板 1、模板定义 建议单独创建template目录,在template目录中创建管理模板文件。 由于模板只有wxml、wxss文件,一个template的模板文件和样式文件只需要命名相同即可,方法则需要在调用的页面中定义 模板文件(wxml):用name区分多个模板 [代码]<template name="packModule"> <view class="packModule">packModule</view> </template> [代码] 模板文件(wxss):自定义模板的样式文件(略),实例中模块相关样式都统一集中在module.wxss中 2、页面引用:(如首页引用) index.wxml: [代码]<!--导入模板--> <import src="./modules/pack.wxml"/> <!--嵌入模板--> <view class="moduleWrap" wx:for="{{moduleInfoList}}" wx:for-item="moduleInfo" wx:key="index"> <!--自由容器模块 里面还有子模块--> <template is="packModule" data="{{moduleInfo}}" wx-if="{{moduleInfo.style == 5}}"></template> <view> [代码] index.wxss: [代码]@import "../../libs/templates/module.wxss"; [代码] 备注: 一个模板文件中可引用多个template,每个template均以name进行区分,页面调用的时候也是以name指向对应的template; template模板没有配置文件(.json)和业务逻辑文件(.js),所以template模板中的变量引用和业务逻辑事件都需要在引用页面的js文件中进行定义; 三、Component组件: [图片] 1. 组件创建: 新建component目录——创建子目录——新建Component(如示例组件:dialog组件) 示例dialog组件也由4个文件构成,与page文件类型相同,但是js文件和json文件与页面有所不同。 dialog.wxml: [代码]<view class="dialog" wx:if="{{ isShow }}"> <!-- 遮罩层 --> <view class="dialog_mask" catchtouchmove="_catchTouch"></view> <!-- 内容 --> <view class="container" catchtouchmove="_catchTouch"> <view class="title" wx:if="{{title}}">{{title}}</view> <view class="content" wx:if="{{content}}"> {{content}} </view> <view class="footer"> <view class="btn cancel_btn" wx:if="{{showCancelButton}}" bindtap="hide" style='background-color:{{globalColor}};'>{{cancelButton}}</view> <view class="btn comfirm_btn" bindtap="comfirm" style='background-color:{{globalColor}};'>{{confirmButton}}</view> </view> </view> </view> [代码] dialog.json: [代码]{ "component": true, "usingComponents": {} } [代码] dialog.wxss:(组件对应 wxss 文件的样式,只对组件wxml内的节点生效) [代码]/* components/dialog/dialog.wxss */ .dialog { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; position: fixed; top:0; z-index: 9999; } ... [代码] dialog.js: [代码]/* * dialog 模态对话框 * Props * 通过事件调用组件 * globalColor:全局色 * Event * show 模态对话框 @param {Object} 配置参数 * hide 模态对话框 * Example * 1、页面中引用dialog:(示例index页面) * 1-1:index.json中声明组件引用 * { * "usingComponents": { * "dialog": "../../components/dialog/dialog" * } * 1-2:index.wxml引用模板 * <dialog id='dialog' global-color="{{globalColor}}"></toast> * 1-3:index页面所引用js文件中,获取组件实例 * onReady: function () { * //获得组件 * this.dialog = this.selectComponent("#dialog"); * } * 2、根据业务条件进行调用: * 2-1、页面中调用: * this.dialog.show({ * title: "提交成功", * content: "描述信息", * cancelButton: "取 消", * showCancelButton:false,//是否显示取消按钮,可缺省,默认不显示 * confirmButton: '确 定', * callback: function () {}//确定按钮回调函数,可缺省,默认只关闭对话框 * }); * 2-1、页面组件中调用: * getCurrentPages()[getCurrentPages().length - 1].dialog.show(...);//传参同上 * 备注:不直接在组件ready中获取dialog组件,会产生多个组件实例,故直接调用页面已有组件实例方法 */ Component({ /** * 组件的属性列表 */ properties: { 'globalColor': String }, /** * 组件的初始数据 */ data: { isShow: false, title: '标题',// 弹窗标题 content: "", // 弹窗内容 cancelButton: '取 消', showCancelButton:false,//是否显示"取消"按钮 confirmButton: '确 定', callback: null //回调函数 }, /** * 组件的方法列表 */ methods: { //隐藏信息提示 hide() { this.setData({ isShow: !this.data.isShow }) }, // 阻止页面滚动 _catchTouch: function () { return; }, //展示信息提示 show(options) { this.setData({ isShow: !this.data.isShow, callback:null }); let _this = this; // 通过options参数配置 if (options) { this.setData(options); } }, // 确定回调 comfirm(){ this.hide(); this.data.callback && this.data.callback();//执行各dialog的回调逻辑 } } }) [代码] 2、页面引用: index.json:(需在json配置文件中进行配置开启使用组件) [代码]{ "usingComponents": { "dialog": "/components/dialog/dialog" } } [代码] index.wxml:(模板文件中引用) [代码]<!-- 自定义dialog组件 --> <dialog id='dialog' global-color="{{globalColor}}"></dialog> [代码] 四、注意点: 1、component组件中扩展自定义节点: 在组件模板中可以提供一个 <slot> 节点,用于承载组件引用时提供的子节点。 默认情况下,一个组件的wxml中只能有一个slot。需要使用多slot时,可以在组件js中声明启用,以不同的 name 来区分。 [代码]Component({ options: { multipleSlots: true // 在组件定义时的选项中启用多slot支持 } }) [代码] 2、component组件样式注意点: 组件对应 wxss 文件的样式,只对组件wxml内的节点生效。 2-1、组件和引用组件的页面不能使用id选择器(#a)、属性选择器([a])和标签名选择器,请改用class选择器。 2-2、组件和引用组件的页面中使用后代选择器(.a .b)在一些极端情况下会有非预期的表现,如遇,请避免使用。 2-3、子元素选择器(.a>.b)只能用于 view 组件与其子节点之间,用于其他组件可能导致非预期的情况。 2-4、继承样式,如 font 、 color ,会从组件外继承到组件内。 2-5、除继承样式外, app.wxss 中的样式、组件所在页面的的样式对自定义组件无效(除非更改组件样式隔离选项)。 [代码] #a { } /* 在组件中不能使用 */ [a] { } /* 在组件中不能使用 */ button { } /* 在组件中不能使用 */ .a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */ [代码] 五、组件间通信与事件: 1、父组件(调用页面)向子组件传值通讯: 通过properties向自定义组件传递数据 2、子组件向父组件(调用页面)传值通讯: 1、监听事件 自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件 [代码]<!-- 当自定义组件触发“myevent”事件时,调用“onMyEvent”方法 --> <component-tag-name bindmyevent="onMyEvent" name="{{name}}" /> Page({ data:{ name:"test" }, onMyEvent: function(e){ e.detail // 自定义组件触发事件时提供的detail对象 } }) [代码] 2、触发事件 自定义组件触发事件时,需要使用 triggerEvent 方法,指定事件名、detail对象和事件选项: [代码]<!-- 在自定义组件中 --> <view>{{name}}</view> <button bindtap="onTap">点击这个按钮将触发“myevent”事件</button> Component({ properties: { name: { type: String, value: '' } }, methods: { onTap: function(){ var myEventDetail = {} // detail对象,提供给事件监听函数 var myEventOption = {} // 触发事件的选项 this.triggerEvent('myevent', myEventDetail, myEventOption) } } }) [代码] 总结 自定义组件,可以理解为一个自定义的标签,页面的一个片段,可以分为template方式和component组件方式实现;如果是简单的内容展示,逻辑单一,使用template方式即可,但如果每一个组件都有自己的业务逻辑,各自独立,建议使用component组件方式实现,灵活性更高。 参考文献 官方文档
2019-06-20 - 不在以下 request 合法域名列表中
这个要多久才会生效? 我昨天配好了域名, 今天还提示这个。 备注:真机上和模拟器上都能访问,但是模拟器上报错,但是真机不报错 以下是图片: [图片]
2018-08-12