vue 自动化路由实现
1、需求描述
在写vue的项目中,一般情况下我们每添加一个新页面都得添加一个新路由。为此我们在项目中会专门的一个文件夹来管理路由,如下图所示

那么有没有一种方案,能够实现我们在文件夹中新建了一个vue文件,就自动帮我们添加路由。特别在我们的一个ERP后台项目中,我们几乎都是一个文件夹下有很多子文件,子文件中一般包含index.vue, detail.vue, edit.vue,分别对应的事列表页,详情页和编辑页。

上图是我们的文件目录,views文件夹中存放的是所有的页面,goodsPlanning是一级目录,onNewComplete和thirdGoods是二级目录,二级目录中存放的是具体的页面,indexComponents中存放的是index.vue的文件,editComponents也是同样的道理。index.vue对应的路由是/goodsPlanning/onNewComplete, edit.vue对应的路由是/goodsPlanning/onNewComplete/edit,detail.vue也是同样的道理。所以我们的文件夹和路由是完全能够对应上的,只要知道路由,就能很快的找到对应的文件。那么有没有办法能够读取我们二级目录下的所有文件,然后根据文件名来生成路由呢?答案是有的
2 、require.context介绍
简单说就是:有了require.context,我们可以得到指定文件夹下的所有文件
require.context(directory, useSubdirectories = false, regExp = /^\.\//);require.context有三个参数:
- directory:说明需要检索的目录
- useSubdirectories:是否检索子目录
- regExp: 匹配文件的正则表达式
require.context()的返回值,有一个keys方法,返回的是个数组
let routers = require.context('VIEWS', true).keys()
console.log(routers)
通过上面的代码,我们打印出了所有的views文件夹下的所有文件和文件夹,我们只要写好正则就能找到我们所需要的文件
3、 直接上代码
import Layout from 'VIEWS/layout/index'
/**
* 正则 首先匹配./ ,然后一级目录,不包含components的二级目录,以.vue结尾的三级目录
*/
let routers = require.context('VIEWS', true, /\.\/[a-z]+\/(?!components)[a-z]+\/[a-z]+\.vue$/i).keys()
let indexRouterMap = {} // 用来存储以index.vue结尾的文件,因为index.vue是列表文件,需要加入layout(我们的菜单),需要keepAlive,需要做权限判断
let detailRouterArr = [] // 用来存储以非index.vue结尾的vue文件,此类目前不需要layout
routers.forEach(item => {
const paths = item.match(/[a-zA-Z]+/g) //paths中存储了一个目录,二级目录,文件名
const routerChild = { //定义路由对象
path: paths[1],
name: `${paths[0]}${_.upperFirst(paths[1])}`, //upperFirst,lodash 首字母大写方法
component(resolve) {
require([`../../views${item.slice(1)}`], resolve)
},
}
if (/index\.vue$/.test(item)) { //判断是否以index。vue结尾
if (indexRouterMap[paths[0]]) { //判断一级路由是否存在,存在push二级路由,不存在则新建
indexRouterMap[paths[0]].children.push(routerChild)
} else {
indexRouterMap[paths[0]] = {
path: '/' + paths[0],
component: Layout,
children: [routerChild]
}
}
} else { //不以index.vue结尾的,直接添加到路由中
detailRouterArr.push({
path: item.slice(1, -4), //渠道最前面的 . 和最后的.vue
name: `${paths[0]}${_.upperFirst(paths[1])}${_.upperFirst(paths[2])}`,
component(resolve) {
require([`../../views${item.slice(1)}`], resolve)
},
meta: {
noCache: true, //不keepAlive
noVerify: true //不做权限验证
}
})
}
})
export default [
...Object.values(indexRouterMap),
...detailRouterArr,
/**
* dashboard单独处理下
*/
{
path: '',
component: Layout,
redirect: 'dashboard',
children: [
{
path: 'dashboard',
component: () => import('VIEWS/dashboard/index'),
name: 'dashboard',
meta: { title: '首页', noCache: true, noVerify: true }
}
]
},
]简简单单的几十行代码就实现了所有的路由功能,再也不用一行一行的写路由文件了。可能你的文件管理方式和我的不一样,但是只要稍微改改正则就行了。
4、 注意
- 不能用import引入路由,因为用import引入不支持变量
- 不能用别名,找了半天问题,才知道用变量时也不能用别名,所以我用的都是相对路径
5、 好处
- 不用在添加路由了,这个就不说了,明眼人都看得出来
- 知道了路由,一个能找到对应的文件,以前我们团队就出现过,乱写path的情况
- 更好的控制验证和keepAlive
原文链接:https://www.cnblogs.com/mianbaodaxia/p/11452123.html
收起阅读 »前端自测清单(前端八股文)
缘起
这篇文章主要列举一些自己想到的面试题目,让大家更加熟悉前端八股文。
先从性能优化开始吧。性能优化大体可以分为两个,
- 运行时优化
- 加载时优化
加载时优化
网络优化
- dns寻址过程
- tcp的三次握手和四次挥手,以及为何要三次和为何要四次
- https的握手过程,以及对称加密和非对称加密的区别,什么是中间人劫持,ca证书包括哪些内容
- http1.0,http1.1以及http2.0的区别,多路复用具体指的是什么,keep-alive具体如何体现
- cdn的原理,cdn什么情况下会回源,cdn的适用场景
- 浏览器缓存有哪几种,它们的区别是什么,什么时候发生缓存,如何决定缓存哪些文件
- 了解过websocket么,解释一下websocket的作用
渲染优化
- 关键渲染路径优化,什么是关键渲染路径,分别如何优化
- 优化体积,webpack的分包策略,如何配置优化,如何提高构建速度,tree-shaking是什么
- cssom 的优化,以及html解析过程中,遇到哪些tag会阻塞渲染
- 雅虎军规说,css尽量放到head里,js放到下方,那么移动端适配的flexiblejs为何要放到css上方呢
- 影响回流重绘的因素有哪些,如何避免回流,以及bfc是什么,bfc有什么特性,清除浮动的原理是什么
场景:如何优化首屏
除了上以及下面说到的,这里也是分两个层面,
- 加载时优化
- 运行时优化
加载
- 首屏请求和非首屏请求拆分
- 图片都应该使用懒加载的形式加载
- 使用preload预加载技术,以及prefetch的dns预解析
- 与首屏无关的代码可以加async甚至是defer等待网页加载完成后运行
运行
这里跟加载的异常耦合,另作分析吧。
运行时优化
- 虚拟长列表渲染
- 图片懒加载
- 使用事件委托
- react memo以及pureComponent
- 使用SSR
- 。。。
以及一些比较骚的操作,只能特定场景使用,
- serviceWorker劫持页面
- 利用worker
更新一波,性能优化之外的面试题,
底层
- V8是如何实现GC的
- JS的let,const,call stack,function context,global context。。。的区别
- this的指向,箭头函数中this和function里的this有什么区别
- 原型链是什么,继承呢,有几种继承方式,如何实现es6的class
- eventloop是什么,浏览器的eventloop和nodejs的eventloop有什么区别,nexttick是什么
- commonjs和AMD,CMD的区别,以及跟ES MODULE的区别
- 说说require.cache
- 了解过,洋葱模型没有,它是如何实现的
- 说说nodejs中的流,也就是stream
- 你用过ts,说说你常用的ts中的数据类型
- js的数据类型,weakMap,weakSet和Map以及Set的区别是什么
- 为何0.1+0.2 不等于0.3,如何解决这个问题
- js的类型转换
- 正则表达式
- 对象循环引用会发生什么问题
- 如何捕获异步的异常,你能说出几种方案
CSS相关
- position有哪几种属性,它们的区别是什么
- 如何实现垂直居中,移动端的呢
- margin设置百分比,是依据谁的百分比,padding呢
- 怪异盒模型和一般盒模型有什么区别
- flex:1代表什么,flex-shrink和flex-grow有什么区别
- background-size origin基准点在哪里
- 移动端1px解决方案,以及为何会产生这个问题
- 移动端高清屏图片的解决方案
- 说说GPU加速
跨端
- RN 实现原理
- 小程序实现原理
- webview跟h5有什么区别
- RPC 是什么
- JSBridge 原理是什么
- 网页唤起app的原理是什么
服务端
- oauth2了解过没有,sso呢
- JWT 如何实现的
网络
除了之前提到的网络问题,当然还有很多,比如
- 为何使用1x1的gif进行请求埋点
- TCP 如何进行拥塞控制
安全
- csrf是什么,防范措施是什么
- xss如何防范
浏览器相关
- 跨域是如何产生的,如何解决
- 如何检查性能瓶颈
- 打开页面白屏,如何定位问题,或者打开页面CPU100%,如何定位问题
- jsonp是什么,为何能解决跨域,使用它可能会产生什么问题
- base64会产生什么问题
- event.target和event.currTarget有什么区别
框架相关
- react和vue的区别
- react的调度原理
- setstate为何异步
- key的作用是什么,为何说要使用唯一key,react的diff算法是如何实现的,vue的呢
- react的事件系统是如何实现的
- react hook是如何实现的
- react的通信方式,hoc的使用场景
- 听过闭包陷阱么,为何会出现这种现象,如何避免
- vue的响应式原理
- 为何vue3.x用的是proxy而不是object.defineProperties
- vue是如何实现对数据的监听的,对数组呢
- vue中的nexttick是如何实现的
- fiber是什么,简单说说时间切片如何实现,为何vue不需要时间切片
- webpack是如何实现的,HMR是如何实现的,可以写个简单的webpack么,webpack的执行流程是怎样的
- koa源码实现,洋葱模型原理,get/post等这些方法如何set入koa里的,ctx.body为何能直接改变response的body
- 你简历上写的了解过webpack源码,到哪种程度了(实话说没写koa简单。。
算法相关
- js大整数加法
- 双指针
- 经典排序
- 动态规划
- 贪心算法
- 回溯法
- DFS
- BFS
- 链表操作
- 线性求值
- 预处理,前缀和
项目相关
- 项目中遇到的最大问题是什么,如何解决的
- nodejs作为中间层的作用是什么
场景题(机试)
- 如何实现直播上的弹幕组件,要求不能重叠,仿照b站上的弹幕
- 如何实现动态表单,仿照antd上的form组件
- 实现一个promise(一般不会这样问)
- 实现一个限制请求数量的方法
- 如何实现一个大文件的上传
- 实现一个eventEmitter
- 实现一个new,call,bind,apply
- 实现一个throttle,debound
- 实现promise.then,finally,all
- 实现继承,寄生组合继承,instanceof
- 实现Generator,Aynsc
20.11.20 更新来了
- React 生命周期,(分三个阶段进行回答,挂载阶段,更新阶段以及卸载阶段)
- Vue 生命周期 以及其父子组件的生命周期调度顺序
- 如果让你用强缓存或者协商缓存来缓存资源的话,你会如何使用
- 作用域是什么,作用域链呢?(这题我想了下,不会利用语言去表达这个东西。。)
目前暂时想到这些,后来有想到的会补充上去。
原文链接:https://steinw.cc/zi-ce-qing-dan/
前端如何进行用户权限管理
【前端如何进行用户权限管理】
1:问题:
假如在做一个管理系统,面向老师学生的,学生提交申请,老师负责审核(或者还需要添加其他角色,功能权限都不同)。
现在的问题是,每种角色登录看到的界面应该都是不一样的,那这个页面的区分如何实现呢?
2:要不要给老师和学生各自设计一套页面?这样工作量是不是太大了,并且如果还要加入其它角色的话,难道每个角色对应一套代码?
所以我们需要用一套页面适应各种用户角色,并根据身份赋予他们不同权限
3:权限设计与管理是一个很复杂的问题,涉及的东西很多,相比前端,更偏向于后端,在搜集相关资料的过程中,发现掺杂了许多数据库之类的知识,以及几个用于权限管理的java框架,比如spring,比如shiro等等,都属于后端的工作
4:那我们前端能做什么呢?
权限的设计中比较常见的就是RBAC基于角色的访问控制,基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。
一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。
在Angular构建的单页面应用中,要实现这样的架构我们需要额外多做一些事.从整体项目上来讲,大约有3处地方,前端工程师需要进行处理.
1. UI处理(根据用户拥有的权限,判断页面上的一些内容是否显示)
2. 路由处理(当用户访问一个它没有权限访问的url时,跳转到一个错误提示的页面)
3. HTTP请求处理(当我们发送一个数据请求,如果返回的status是401或者401,则通常重定向到一个错误提示的页面)
如何实现?
首先需要在Angular启动之前就获取到当前用户的所有的permissions,然后比较优雅的方式是通过一个service存放这个映射关系.对于UI处理一个页面上的内容是否根据权限进行显示,我们应该通过一个directive来实现.当处理完这些,我们还需要在添加一个路由时额外为其添加一个"permission"属性,并为其赋值表明拥有哪些权限的角色可以跳转这个URL,然后通过Angular监听routeChangeStart事件来进行当前用户是否拥有此URL访问权限的校验.最后还需要一个HTTP拦截器监控当一个请求返回的status是401或者403时,跳转页面到一个错误提示页面.
大致上的工作就是这些,看起来有些多,其实一个个来还是挺好处理的.
在Angular运行之前获取到permission的映射关系

Angular项目通过ng-app启动,但是一些情况下我们是希望Angular项目的启动在我们的控制之中.比如现在这种情况下,我就希望能获取到当前登录用户的所有permission映射关系后,再启动Angular的App.幸运的是Angular本身提供了这种方式,也就是angular.bootstrap().看的仔细的人可能会注意到,这里使用的是$.get(),没有错用的是jQuery而不是Angular的$resource或者$http,因为在这个时候Angular还没有启动,它的function我们还无法使用.
进一步使用上面的代码可以将获取到的映射关系放入一个service作为全局变量来使用.

在取得当前用户的权限集合后,我们将这个集合存档到对应的一个service中,然后又做了2件事:
(1) 将permissions存放到factory变量中,使之一直处于内存中,实现全局变量的作用,但却没有污染命名空间.
(2) 通过$broadcast广播事件,当权限发生变更的时候.
如何确定UI组件的依据权限进行显隐


扩展一下之前的factory:

路由上的依权限访问
这一部分的实现的思路是这样: 当我们定义一个路由的时候增加一个permission的属性,属性的值就是有哪些权限才能访问当前url.然后通过routeChangeStart事件一直监听url变化.每次变化url的时候,去校验当前要跳转的url是否符合条件,然后决定是跳转成功还是跳转到错误的提示页面.
router.js:
mainController.js 或者 indexController.js (总之是父层Controller)
这里依然用到了之前写的hasPermission,这些东西都是高度可复用的.这样就搞定了,在每次view的route跳转前,在父容器的Controller中判断一些它到底有没有跳转的权限即可.
HTTP请求处理
这个应该相对来说好处理一点,思想的思路也很简单.因为Angular应用推荐的是RESTful风格的接口,所以对于HTTP协议的使用很清晰.对于请求返回的status code如果是401或者403则表示没有权限,就跳转到对应的错误提示页面即可.

当然我们不可能每个请求都去手动校验转发一次,所以肯定需要一个总的filter.代码如下:
写到这里我们就基本实现了在这种前后端分离模式下,前端部分的权限管理和控制。
原文链接:https://blog.csdn.net/jnshu_it/article/details/77511588
彻底解决小程序无法触发SESSION问题
一、首先找到第一次发起网络请求的地址,将服务器返回set-cookie当全局变量存储起来
wx.request({
......
success: function(res) {
console.log(res.header);
//set-cookie:PHPSESSID=ic4vj84aaavqgb800k82etisu0; path=/; domain=.fengkui.net
// 登录成功,获取第一次的sessionid,存储起来
// 注意:Set-Cookie(开发者工具中调试全部小写)(远程调试和线上首字母大写)
wx.setStorageSync("sessionid", res.header["Set-Cookie"]);
}
})二、请求时带上将sessionid放入request的header头中传到服务器,服务器端可直接在cookie中获取
wx.request({
......
header: {
'content-type': 'application/json', // 默认值
'cookie': wx.getStorageSync("sessionid")
//读取sessionid,当作cookie传入后台将PHPSESSID做session_id使用
},
success: function(res) {
console.log(res)
}
})三、后台获取cookie中的PHPSESSID,将PHPSESSID当作session_id使用
<?php
// 判断$_COOKIE['PHPSESSID']是否存在,存在则作session_id使用
if ($_COOKIE['PHPSESSID']) {
session_id($_COOKIE['PHPSESSID']);
}
session_start();
echo session_id();
原文链接:https://blog.csdn.net/qq_41654694/article/details/85991846
收起阅读 »vue 重复点击菜单,路由重复报错
报错信息
报错原因
vue-router在3.0版本以上时 ,回调形式改成了promise api,返回的是promise,如果没有捕获到错误,控制台始终会出现如上图的报错
node_module/vue-router/dist/vue-router.js 搜VueRouter.prototype.push
解决方法
1.降低vue-router的版本
npm i vue-router@3.0 -S2.在vue.use(Router)使用路由插件之前插入如下代码
//获取原型对象上的push函数
const originalPush = Router.prototype.push
//修改原型对象中的push方法
Router.prototype.push = function push (location) {
return originalPush.call(this, location).catch(err => err)
}3.捕获异常
// 捕获router.push异常
this.$router.push(route).catch(err => {
console.log('输出报错',err)4.补齐router第三个参数
// 补齐router.push()的第三个参数
this.$router.push(route, () => {}, (e) => {
console.log('输出报错',e)
})本文链接:https://blog.csdn.net/pinbolei/article/details/115620529
深入理解vue中的slot与slot-scope
写在前面
单个插槽 | 默认插槽 | 匿名插槽
<template>
<div class="father">
<h3>这里是父组件</h3>
<child>
<div class="tmpl">
<span>菜单1</span>
<span>菜单2</span>
<span>菜单3</span>
<span>菜单4</span>
<span>菜单5</span>
<span>菜单6</span>
</div>
</child>
</div>
</template>子组件:
<template>
<div class="child">
<h3>这里是子组件</h3>
<slot></slot>
</div>
</template>在这个例子里,因为父组件在<child></child>里面写了html模板,那么子组件的匿名插槽这块模板就是下面这样。也就是说,子组件的匿名插槽被使用了,是被下面这块模板使用了。
<div class="tmpl">
<span>菜单1</span>
<span>菜单2</span>
<span>菜单3</span>
<span>菜单4</span>
<span>菜单5</span>
<span>菜单6</span>
</div>最终的渲染结果如图所示:

注:所有demo都加了样式,以方便观察。其中,父组件以灰色背景填充,子组件都以浅蓝色填充。具名插槽
<template>
<div class="father">
<h3>这里是父组件</h3>
<child>
<div class="tmpl" slot="up">
<span>菜单1</span>
<span>菜单2</span>
<span>菜单3</span>
<span>菜单4</span>
<span>菜单5</span>
<span>菜单6</span>
</div>
<div class="tmpl" slot="down">
<span>菜单-1</span>
<span>菜单-2</span>
<span>菜单-3</span>
<span>菜单-4</span>
<span>菜单-5</span>
<span>菜单-6</span>
</div>
<div class="tmpl">
<span>菜单->1</span>
<span>菜单->2</span>
<span>菜单->3</span>
<span>菜单->4</span>
<span>菜单->5</span>
<span>菜单->6</span>
</div>
</child>
</div>
</template><template>
<div class="child">
// 具名插槽
<slot name="up"></slot>
<h3>这里是子组件</h3>
// 具名插槽
<slot name="down"></slot>
// 匿名插槽
<slot></slot>
</div>
</template>显示结果如图:

作用域插槽 | 带数据的插槽
匿名插槽
<slot></slot>
具名插槽
<slot name="up"></slot>但是作用域插槽要求,在slot上面绑定数据。也就是你得写成大概下面这个样子。
<slot name="up" :data="data"></slot>
export default {
data: function(){
return {
data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
}
},
}我们前面说了,插槽最后显示不显示是看父组件有没有在child下面写模板,像下面那样。
<child>
html模板
</child><template>
<div class="father">
<h3>这里是父组件</h3>
<!--第一次使用:用flex展示数据-->
<child>
<template slot-scope="user">
<div class="tmpl">
<span v-for="item in user.data">{{item}}</span>
</div>
</template>
</child>
<!--第二次使用:用列表展示数据-->
<child>
<template slot-scope="user">
<ul>
<li v-for="item in user.data">{{item}}</li>
</ul>
</template>
</child>
<!--第三次使用:直接显示数据-->
<child>
<template slot-scope="user">
{{user.data}}
</template>
</child>
<!--第四次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽-->
<child>
我就是模板
</child>
</div>
</template>子组件:
<template>
<div class="child">
<h3>这里是子组件</h3>
// 作用域插槽
<slot :data="data"></slot>
</div>
</template>
export default {
data: function(){
return {
data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
}
}
}github
收起阅读 »JavaScript 逐点突破系列 -- 变幻莫测的this指向
JavaScript 逐点突破系列 – 变幻莫测的this指向
this指向
let button = document.getElemetById('button')
button.onclick = function () {
console.log(this) //button对象
}全局环境
浏览器环境下
console.log(this) // windownode环境下
console.log(this) // module.exportsvar object = {
name: 'object',
getName: function() {
console.log(this)
}
}
var bar = object.getName // 只是函数声明并未调用
object.getName() // object对象
window.object.getName() // object对象
/* 函数被多层对象所包含,如果函数被最外层对象调用,this指向
的也只是它上一级的对象。*/
bar() // window对象let fn = function(){
this.id = 'xiaoMing'
console.log(this.id)
}
let fn1 = new fn() //this指向fn1对象new 的内部原理
【1】创建一个空对象 obj;
【2】把 child 的__proto__ 指向构造函数 parent 的原型对象 prototype,此时便建立了 obj 对象的原型链:child ->parent.prototype->Object.prototype->null
【3】在 child 对象的执行环境调用 parent 函数并传递参数。
【4】考察第 3 步的返回值,如果无返回值 或者 返回一个非对象值,则将 child 作为新对象返回;否则会将 result 作为新对象返回。this绑定的是返回的对象。
function fn(){
this.num = 10;
}
fn.num = 20;
fn.prototype.num = 30;
fn.prototype.method = function() {
console.log(this.num);
}
var prototype = fn.prototype
var method = prototype.method
new fn().method() // 10
prototype.method() // 30
method() // undefinedfunction foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1上面代码之中,只有一个this,就是函数foo的this,所以t1、t2、t3都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的this,它们的this其实都是最外层foo函数的this。
除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments、super、new.target。
由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。
不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
修改this指向
apply、call、bind用法
func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])
func.bind(this, arg1, arg2)()原文链接:https://blog.csdn.net/weixin_45495667/article/details/108801000
收起阅读 »js实现函数防抖节流
一、什么是函数防抖跟节流?
二、JavaScript实现
1.函数防抖
function debounce(callback, wait) {
let timer = null;
return function(){
let _this = this,
arg = arguments;
if(!!timer){
clearTimeout(timer)
timer = null
}
timer = setTimeout(function(){
callback.call(_this,arg)
},wait)
}
}
let _debounce = debounce(function() {
console.log('dshdihdi')
}, 1000)2.函数节流
// 函数节流,时间戳版本
function throttle(callback, wait) {
let time = new Date().getTime();
return function() {
let _this = this,
arg = arguments;
let nowTime = new Date().getTime();
if (nowTime - time >= wait){
callback.call(_this,...arg)
time = nowTime
}
}
}
let _throttle = throttle(function(e) {
console.log(e)
console.log(arguments)
}, 1000)收起阅读 »
JavaScript new 操作符
new 操作符做的事情 - 01
new 操作符做的事情 - 02
- 创建一个全新的对象 (无原型的Object.create(null)) 。
- 目的是保存 new 出来的实例的所有属性。
- 将构造函数的原型赋值给新创建的对象的原型。
- 目的是将构造函数原型上的属性继承下来。
- 调用构造函数,并将 this 指向新建的对象。
- 目的是让构造函数内的属性全部转交到该对象上,使得 this 指向改变,方法有三 : apply、call、bind 。
- 判断构造函数调用的方式,如果是 new 的调用方式,则返回经过加工后的新对象,如果是普通调用方式,则直接返回构造函数调用时的返回值。
function myNew(Constructor, ...args) {
// 判断 Constructor 参数是否是函数
if (typeof Constructor !== 'function') {
return 'Constructor.apply is not a function';
};
// 1、创建了一个全新的对象。
let newObject = {};
// 2、将对象链接到这个函数的 prototype 对象上。
newObject.__proto__ = Constructor.prototype;
// 此处是把 1 / 2 步结合到一起
// const newObject = Object.create(Constructor.prototype);
// 3、执行构造函数,
// 并将 this 绑定到新创建的对象上。
let result = Constructor.apply(newObject, args);
// 4. 判断构造函数执行返回的结果是否是引用数据类型,
// 若是则返回构造函数执行的结果,
// 否则返回创建的对象。
if ((result !== null && typeof result === 'object') || (typeof result === 'function')) {
return result;
} else {
return newObject;
};
};
// 需要被 new 的函数
function NewTest(args) {
this.dataValue = args;
return this;
};
// 定义参数
let dataObj = {
sname: '杨万里',
number: 3,
web: 'vue'
};
let dataArray = [5, 'uniApp', '范仲淹'];
// 执行 myNew 函数
let test = myNew(NewTest, 1, 'JavaScript', '辛弃疾', dataObj, dataArray);
console.log(test); // NewTest {dataValue: Array(5)}本文链接:https://blog.csdn.net/weixin_51157081/article/details/115577751
微信小程序-使用canvas绘制图片,下载,分享
需求, 使用canvas绘制图片和文字, 生成图片
<!--pages/canvas/canvas.wxml-->
<view>
<view class="container">
<button bind:tap="start" class="start" size="mini">选择图片</button>
<button bind:tap="downloadCanvas" class="downloadCanvas" size="mini">下载</button>
<button bind:tap="share" class="share" size="mini">分享</button>
<!-- 分享图片功能 -->
</view>
<text>canvas区域</text>
<canvas style='width:{{canvasWidth}}px;height:{{canvasHeight}}px;border: 1px solid grey;' canvas-id='myCanvas'></canvas>
<image v-if="previewImage" :src="{{canvasImage}}"></image>
</view>
<!--pages/canvas/canvas.wxml-->
<view>
<view class="container">
<button bind:tap="start" class="start" size="mini">选择图片</button>
<button bind:tap="downloadCanvas" class="downloadCanvas" size="mini">下载</button>
<button bind:tap="share" class="share" size="mini">分享</button>
<!-- 分享图片功能 -->
</view>
<text>canvas区域</text>
<canvas style='width:{{canvasWidth}}px;height:{{canvasHeight}}px;border: 1px solid grey;' canvas-id='myCanvas'></canvas>
<image v-if="previewImage" :src="{{canvasImage}}"></image>
</view>图片功能
先设置canvas区域的宽度和高度
js代码
data: {
title: 'canvas绘制图片',
canvasWidth: '', // canvas宽度
canvasHeight: '', // canvas高度
imagePath: '', // 分享的图片路径
leftMargin: 0,
topMargin: 0,
imgInfo: {},
ctx: [],
canvasImage: '',
previewImage: false,
imgProportion: 0.8, // 图片占canvas画布宽度百分比
imgToTop: 100 // 图片到canvas顶部的距离
},
onLoad: function(options) {
var that = this
var sysInfo = wx.getSystemInfo({
success: function(res) {
that.setData({
canvasWidth: res.windowWidth,
// 我这里选择canvas高度是系统高度的80%
canvasHeight: res.windowHeight * 0.8
})
// 根据图片比例, 使图片居中
let leftMargin = (res.windowWidth * (1 - that.data.imgProportion)) / 2
that.setData({
leftMargin
})
}
})
}接下来下选择图片
// 点击选择图片按钮触发
start: function() {
let that = this
let ctx = wx.createCanvasContext('myCanvas')
// 设置canvas背景色, 否则制作的图片是透明的
ctx.setFillStyle('#f8f8f8')
ctx.fillRect(0, 0, that.data.canvasWidth, that.data.canvasHeight)
this.addImage(ctx)
},
// 添加图片
addImage: function(ctx) {
var that = this;
let imgInfo = that.data.imgInfo
var path
wx.chooseImage({
count: '1',
success(res) {
wx.getImageInfo({
src: res.tempFilePaths[0],
success: function(response) {
// 返回的response里有图片的临时路径和图片信息(高度/宽度)
that.setData({
imgInfo: response,
path: response.path
})
that.drawImage(ctx)
}
})
}
})
this.addTitle(ctx)
},绘制文字和图
// 绘制图片
drawImage(ctx) {
let that = this
let imgInfo = that.data.imgInfo
let path = that.data.path
// 计算图片宽度 宽度固定 高度等比缩放
let imgWidth = that.data.canvasWidth * that.data.imgProportion
let imgHeight = imgInfo.height / imgInfo.width * imgWidth
// drawImage参数, 下面会说明
ctx.drawImage(path, 0, 0, imgInfo.width, imgInfo.height, that.data.leftMargin, that.data.imgToTop, imgWidth, imgHeight)
ctx.draw()
that.data.previewImage = true
},
//绘制文字
addTitle: function(ctx) {
var str = this.data.title
ctx.font = 'normal bold 16px sans-serif';
ctx.setTextAlign('center'); // 文字居中
ctx.setFillStyle("#222222");
ctx.fillText(str, this.data.canvasWidth / 2, 45) // 文字位置
},drawImage方法
drawImage 方法允许在 canvas 中插入其他图像( img 和 canvas 元素) 。drawImage函数有三种函数原型:
drawImage(image, dx, dy) 在画布指定位置绘制原图
drawImage(image, dx, dy, dw, dh) 在画布指定位置上按原图大小绘制指定大小的图
drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) 剪切图像,并在画布上定位被剪切的部分 从 1.9.0 起支持
参数描述
image
所要绘制的图片资源(网络图片要通过 getImageInfo / downloadFile 先下载)
s
需要绘制到画布中的,image的矩形(裁剪)选择框的左上角 x 坐标
sy
需要绘制到画布中的,image的矩形(裁剪)选择框的左上角 y 坐标
sWidth
需要绘制到画布中的,image的矩形(裁剪)选择框的宽度
sHeight
需要绘制到画布中的,image的矩形(裁剪)选择框的高度
dx
image的左上角在目标 canvas 上 x 轴的位置
dy
image的左上角在目标 canvas 上 y 轴的位置
dWidth
在目标画布上绘制imageResource的宽度,允许对绘制的image进行缩放
dHeight
在目标画布上绘制imageResource的高度,允许对绘制的image进行缩放
下载图片
//点击下载按钮保存canvas图片
downloadCanvas: function() {
let that = this;
// 判断用户是否选择了图片
if (that.data.previewImage) {
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: that.canvasWidth,
height: that.canvasWidth,
destWidth: that.canvasWidth,
destHeight: that.canvasHeight,
canvasId: 'myCanvas',
success: function success(res) {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success(res) {
console.log(res, '保存')
}
})
}
});
} else {
wx.showToast({
title: '请先选择图片',
image: '../../static/img/error.png'
})
}
}分享图片
// 分享图片
share() {
let that = this
if (that.data.previewImage) {
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: that.canvasWidth,
height: that.canvasWidth,
destWidth: that.canvasWidth,
destHeight: that.canvasHeight,
canvasId: 'myCanvas',
success: function success(res) {
wx.showToast({
icon: 'none',
title: '长按图片分享',
duration: 1500
})
setTimeout(
() => {
wx.previewImage({
urls: [res.tempFilePath]
})
}, 1000)
}
})
} else {
wx.showToast({
title: '请先选择图片',
image: '../../static/img/error.png'
})
}
}原文链接:https://www.jianshu.com/p/1e54146b8a26
收起阅读 »H5之外部浏览器唤起微信分享
<script src="mshare.js"></script>//引进mshare.js
<button data-mshare="0">点击弹出原生分享面板</button>
<button data-mshare="1">点击触发朋友圈分享</button>
<button data-mshare="2">点击触发发送给微信朋友</button><script>
var mshare = new mShare({
title: 'Lorem ipsum dolor sit.',
url: 'http://m.ly.com',
desc: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quaerat inventore minima voluptates.',
img: 'http://placehold.it/150x150'
});
$('button').click(function () {
// 1 ==> 朋友圈 2 ==> 朋友 0 ==> 直接弹出原生
mshare.init(+$(this).data('mshare'));
});
</script>/**
* 此插件主要作用是在UC和QQ两个主流浏览器
* 上面触发微信分享到朋友圈或发送给朋友的功能
*/
'use strict';
var UA = navigator.appVersion;
/**
* 是否是 UC 浏览器
*/
var uc = UA.split('UCBrowser/').length > 1 ? 1 : 0;
/**
* 判断 qq 浏览器
* 然而qq浏览器分高低版本
* 2 代表高版本
* 1 代表低版本
*/
var qq = UA.split('MQQBrowser/').length > 1 ? 2 : 0;
/**
* 是否是微信
*/
var wx = /micromessenger/i.test(UA);
/**
* 浏览器版本
*/
var qqVs = qq ? parseFloat(UA.split('MQQBrowser/')[1]) : 0;
var ucVs = uc ? parseFloat(UA.split('UCBrowser/')[1]) : 0;
/**
* 获取操作系统信息 iPhone(1) Android(2)
*/
var os = (function () {
var ua = navigator.userAgent;
if (/iphone|ipod/i.test(ua)) {
return 1;
} else if (/android/i.test(ua)) {
return 2;
} else {
return 0;
}
}());
/**
* qq浏览器下面 是否加载好了相应的api文件
*/
var qqBridgeLoaded = false;
// 进一步细化版本和平台判断
if ((qq && qqVs < 5.4 && os == 1) || (qq && qqVs < 5.3 && os == 1)) {
qq = 0;
} else {
if (qq && qqVs < 5.4 && os == 2) {
qq = 1;
} else {
if (uc && ((ucVs < 10.2 && os == 1) || (ucVs < 9.7 && os == 2))) {
uc = 0;
}
}
}
/**
* qq浏览器下面 根据不同版本 加载对应的bridge
* @method loadqqApi
* @param {Function} cb 回调函数
*/
function loadqqApi(cb) {
// qq == 0
if (!qq) {
return cb && cb();
}
var script = document.createElement('script');
script.src = (+qq === 1) ? '//3gimg.qq.com/html5/js/qb.js' : '//jsapi.qq.com/get?api=app.share';
/**
* 需要等加载过 qq 的 bridge 脚本之后
* 再去初始化分享组件
*/
script.onload = function () {
cb && cb();
};
document.body.appendChild(script);
}
/**
* UC浏览器分享
* @method ucShare
*/
function ucShare(config) {
// ['title', 'content', 'url', 'platform', 'disablePlatform', 'source', 'htmlID']
// 关于platform
// ios: kWeixin || kWeixinFriend;
// android: WechatFriends || WechatTimeline
// uc 分享会直接使用截图
var platform = '';
var shareInfo = null;
// 指定了分享类型
if (config.type) {
if (os == 2) {
platform = config.type == 1 ? 'WechatTimeline' : 'WechatFriends';
} else if (os == 1) {
platform = config.type == 1 ? 'kWeixinFriend' : 'kWeixin';
}
}
shareInfo = [config.title, config.desc, config.url, platform, '', '', ''];
// android
if (window.ucweb) {
ucweb.startRequest && ucweb.startRequest('shell.page_share', shareInfo);
return;
}
if (window.ucbrowser) {
ucbrowser.web_share && ucbrowser.web_share.apply(null, shareInfo);
return;
}
}
/**
* qq 浏览器分享函数
* @method qqShare
*/
function qqShare(config) {
var type = config.type;
//微信好友 1, 微信朋友圈 8
type = type ? ((type == 1) ? 8 : 1) : '';
var share = function () {
var shareInfo = {
'url': config.url,
'title': config.title,
'description': config.desc,
'img_url': config.img,
'img_title': config.title,
'to_app': type,
'cus_txt': ''
};
if (window.browser) {
browser.app && browser.app.share(shareInfo);
} else if (window.qb) {
qb.share && qb.share(shareInfo);
}
};
if (qqBridgeLoaded) {
share();
} else {
loadqqApi(share);
}
}
/**
* 对外暴露的接口函数
* @method mShare
* @param {Object} config 配置对象
*/
function mShare(config) {
this.config = config;
this.init = function (type) {
if (typeof type != 'undefined') this.config.type = type;
try {
if (uc) {
ucShare(this.config);
} else if (qq && !wx) {
qqShare(this.config);
}
} catch (e) {}
}
}
// 预加载 qq bridge
loadqqApi(function () {
qqBridgeLoaded = true;
});
if (typeof module === 'object' && module.exports) {
module.exports = mShare;
} else {
window.mShare = mShare;
} 收起阅读 »














