Shero.

生活百般滋味,你要笑着面对😊


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

工具函数

发表于 2019-07-05 | 分类于 JS

js工具函数

toFixed 精度高的用法
1
2
3
4
function round(number, precision) {
return Math.round(+number + 'e' + precision) / Math.pow(10, precision);
}
* 注意整数使用toFixed 写法需要括号(6).toFixed(2)
switch实现
  • 数组+索引

    1
    2
    3
    function previewWeek(i) {
    return i > 0 && i < 8 ? '星期'+['一','二','三','四','五','六','日'][i-1] : '';
    }
  • map

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function previewWeek(i){
    let weeksMap = new Map([
    [1, '一'],
    [2, '二'],
    [3, '三'],
    [4, '四'],
    [5, '五'],
    [6, '六'],
    [7, '日']
    ]);
    return weeksMap.get(i) ? '星期'+weeksMap.get(i) : '';
    }
过滤数组中的false
1
2
let arr = [1, 0, undefined, null, false];
arr.filter(Boolean);
创建一个纯净的空对象
1
2
let dict = Object.create(null);
dict.__proto__ === "undefined"
合并对象
1
2
3
4
5
6
7
8
9
10
const page = {
current: 1,
pageSize: 10
}

const obj = {
name: '',
sex: ''
}
const params = {...obj, ...page};
解构赋值使用别名
1
2
3
4
5
6
const obj = { x: 1 };
// Grabs obj.x as { x }
const { x } = obj;

// Grabs obj.x as { otherName }
const { x: otherName } = obj;
数组map方法,抽离数组对象中的某个属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const cities = [
{ name: 'Paris', visited: 'no' },
{ name: 'Lyon', visited: 'no' },
{ name: 'Marseille', visited: 'yes' },
{ name: 'Rome', visited: 'yes' },
{ name: 'Milan', visited: 'no' },
{ name: 'Palermo', visited: 'yes' },
{ name: 'Genoa', visited: 'yes' },
{ name: 'Berlin', visited: 'no' },
{ name: 'Hamburg', visited: 'yes' },
{ name: 'New York', visited: 'yes' }
];
const cityNames = Array.from(cities, ({ name}) => name);
console.log(cityNames);
有条件的对象属性
  • 根据一个条件创建两个不同的对象,可以使用展开运算符号来处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const getUser = (emailIncluded) => {
    return {
    name: 'John',
    surname: 'Doe',
    ...emailIncluded && { email : 'john@doe.com' }
    }
    }
    const user = getUser(true);
    console.log(user); // outputs { name: "John", surname: "Doe", email: "john@doe.com" }
    const userWithoutEmail = getUser(false);
    console.log(userWithoutEmail); // outputs { name: "John", surname: "Doe" }
公用的正则表达式
  • 国内座机号码(0341-86091234)

    1
    let reg = /\d{3}-\d{8}|\d{4}-\d{7}/;
  • 中文型姓名

    1
    let reg = /^([\u4e00-\u9fa5·]{2,10})$/;
  • 车牌号

    1
    2
    3
    4
    5
    6
    // 新能源车牌号
    let reg = /[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领 A-Z]{1}[A-HJ-NP-Z]{1}(([0-9]{5}[DF])|([DF][A-HJ-NP-Z0-9][0-9]{4}))$/;
    // 非新能源车牌号
    let reg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领 A-Z]{1}[A-HJ-NP-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/;
    // 新能源车牌号+非新能源车牌号
    let reg = /^([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领 A-Z]{1}[A-HJ-NP-Z]{1}(([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领 A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9 挂学警港澳]{1})$/;
  • 手机号校验

    1
    2
    3
    4
    5
    6
    7
    // 手机号严谨
    let reg = /^1((3[\d])|(4[5,6,7,9])|(5[0-3,5-9])|(6[5-7])|(7[0-8])|(8[\d])|(9[1,8,9]))\d{8}$/;
    // 手机号宽松
    let reg = /^1[3-9]\d{9}$/;
    或者 let reg = /^1[3456789]\d{9}$/;
    // 手机号(最简单的11位校验)
    let reg = /^1\d{10}$/;
  • email地址

    1
    let reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
  • 身份证

    1
    2
    3
    4
    5
    6
    // 一代身份证(15位数)
    let reg = /^\d{8}(0\d|11|12)([0-2]\d|30|31)\d{3}$/;
    // 二代身份证(18位数)
    let reg = /^\d{6}(18|19|20)\d{2}(0\d|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$/;
    // 一代身份证+二代身份证
    let reg = /(^\d{8}(0\d|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/;
年月日格式替换
1
2
3
4
5
6
7
8
9
10
filterDate(date) {
let _date = date.replace(/[年月]/g, '-');
let _day = _date_.replace(/[日]/, '');
return _day;
},

filterDateName(date) {
let [year, month, day] = `${date}`.split('-');
return `${year}年${month}月${day}日`;
},
js简单粗暴截断数组
1
2
3
4
let array = [0, 1, 2, 3, 4, 5];
array.length = 3;
console.log(array);
Output: [0, 1, 2];
随机排列数组中的元素
1
2
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(arr.sort(function() { return Math.random() - 0.5}));
展平多维数组
1
2
let entries = [1, [2, 5], [6, 7], 9];
let flat_entries = [].concat(...entries);
提取唯一值
1
2
3
let entries = [1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 8, 4, 2, 1];
let unique_entries = [...new Set(entries)];
console.log(unique_entries); // [1, 2, 3, 4, 5, 6, 7, 8]
动态属性名称
1
2
3
const dynamic = 'flavour';
let item = { name: 'Coke', [dynamic]: 'Cherry'};
console.log(item); // { name: "Coke", flavour: "Cherry" }

手机号码脱敏显示 前三位和后三位

1
2
3
4
5
6
7
8
9
10
11
secretPhone(phone) {
if(!phone || phone.length !== 11) {
return ''
}
return phone
.match(/(\d{3})(\d{5})(\d{3})/)
.slice(1)
.reduce(function(value, item, index) {
return index === 1 ? value + '*****' : value + item;
});
}

数组乱序

1
2
3
4
const shuffleArray = (arr) => arr.sort(() => Math.random() - 0.5)
// 测试
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(shuffleArray(arr))

滚动到顶部、滚动到底部

  • “smooth “来实现平滑的滚动动画
  • 如scrollToTop方法,scrollToBottom方法
    1
    2
    3
    4
    5
    6
    7
    const scrollToTop = (element) => {
    element.scrollIntoView({ behavior: "smooth", block: "start" })
    }

    const scrollToBottom = (element) => {
    element.scrollIntoView({ behavior: "smooth", block: "end" })
    }

生成随机颜色

1
2
3
const generateRandomHexColor = () => {
return `#${Math.floor(Math.random() * 0xffffff).toString(16)}`;
}
JavaScript常见错误
  • To Learn More

小程序踩坑

发表于 2019-07-04 | 分类于 微信小程序

容易忽视的小程序细节

wx:for属性
* 绑定一个数组,默认数组当前项的下标变量名为index,数组当前项默认为 item
* 实质是遍历Array数组,但在开发中经常会遇到遍历**Object对象**,会使用在多层嵌套的循环中,不可避免的会重定义item与index
1
2
wx:for-item="xxx"
wx:for-index="xxx"

针对对象Object,以上两个属性木有用呀

  • 一般复杂的逻辑,尽量数据结构都是键值对key-value的显示吧,可以让不认真看文档的我,开发效率提高哟
scroll-view
  • 可滚动视图区域 值得注意的是,行内样式的设置

    1
    2
    width: 100%;
    white-space: nowrap; // 不进行换行
  • 会遇到需求就是滚动区域内的布局,可换行,两者之间会冲突,取舍去吧

  • 微信小程序之Android环境下的横向滚动,当遇到不能滚动时,加上overflow-x: auto
wx.navigator 不可跳转到pages是底部tab
1
2
3
wx.switchTab({
url: '/pages/xxx/xxx'
})
wx.getSystemInfoSync获取windowHeight不准确
  • 主要原因在于获取是时机,wx.getSystemInfoSync是在页面初始化的时候就计算了,基本上可以理解为是屏幕高度。所以,最好的方法是使用异步接口,并且在onReady函数中调用。
    1
    2
    3
    4
    5
    6
    7
    onReady() {
    wx.getSystemInfo({
    success({windowHeight}) {
    // todo
    }
    });
    }
无法获取UnionID的问题
  • login获取UID必须满足两个条件:
    • 把小程序和公众号都绑定在开放平台
    • 用户必须已经关注公众号
  • 用wx.getUserInfo获取满足一个条件:
    • 把小程序和公众号都绑定在开放平台
wx.getUserInfo()接口更改问题

*微信小程序最近被吐槽最多的一个更改,就是用户使用wx.getUserInfo(开发和体验版)时不会弹出授权,正式版不受影响。现在授权方式是需要引导用户点击一个授权按钮,然后再弹出授权。

  • 解法很长,请参考:
  • 微信小程序不支持wx.getUserInfo授权的解决方法
  • getUserInfo兼容解决方案
  • 想了解more跳转

blog

发表于 2019-07-03 | 分类于 blog

新建blog

  • hexo new 博客名称
  • 本地预览 hexo s
  • 新建页面 hexo new page “about”
  • 提交发布
    • hexo generate
    • hexo deploy (提交不上时, git config –global user.email/user.name, 删除根目录.deploy_git,重新执行指令)
  • 结束,可以去看你的博客啦
  • git push origin dev -u 把本地dev push到origin的dev -u表示同时建立关联,以后再推送到远程只需git push origin

小程序引入iconfont踩坑

发表于 2019-07-03 | 分类于 微信小程序

小程序引入iconfont踩坑

  • 在阿里巴巴把需要的图标加入购物车
  • 添加到项目,没项目新建一个
  • 下载到本地
  • 找到iconfont.css把代码复制到小程序项目到app.wxss或者拷贝到static,通过css方式引入不想全局引用,可单独在对应wxss中使用

    1
    @import "./static/iconfont/iconfont.wxss"
  • 替换iconfont.css中的@font-face为上面的生成代码

  • 使用

    1
    <text class='iconfont icon-jiazai'></text>
  • 注意是iconfont不是icon 坑了我几个小时,还是踩坑太少

  • 每次重新添加icon到项目需要重新替换@font-face为上面的生成代码,并且把新添加的图标加入iconfont.wxss

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @font-face {
    font-family: 'iconfont'; /* project id 1274544 */
    src: url('//at.alicdn.com/t/font_1274544_5eej6szwu1j.eot');
    src: url('//at.alicdn.com/t/font_1274544_5eej6szwu1j.eot?#iefix') format('embedded-opentype'),
    url('//at.alicdn.com/t/font_1274544_5eej6szwu1j.woff2') format('woff2'),
    url('//at.alicdn.com/t/font_1274544_5eej6szwu1j.woff') format('woff'),
    url('//at.alicdn.com/t/font_1274544_5eej6szwu1j.ttf') format('truetype'),
    url('//at.alicdn.com/t/font_1274544_5eej6szwu1j.svg#iconfont') format('svg');
    }
  • 看起来很简单,可自己就是犯了很多低级错误rve

webpack

发表于 2019-05-06 | 分类于 tool

webpack

有哪些常见 loader 和 plugin,你用过哪些?
  1. loader
    • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
    • url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
    • source-map-loader:加载额外的 Source Map 文件,以方便断点调试
    • image-loader:加载并且压缩图片文件
    • babel-loader:把 ES6 转换成 ES5
    • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
    • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
    • eslint-loader:通过 ESLint 检查 JavaScript 代码
  2. plugin
    • define-plugin:定义环境变量
    • commons-chunk-plugin:提取公共代码
    • uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
loader 和 plugin 的区别是什么?
  1. 不同的作用
    • Loader直译为”加载器”。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。
      Plugin直译为”插件”。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
  2. 不同的用法
    • Loader在module.rules中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options)
      Plugin在plugins中单独配置。 类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。
如何按需加载代码?
  1. Vue UI组件库的按需加载 为了快速开发前端项目,经常会引入现成的UI组件库如ElementUI、iView等,但是他们的体积和他们所提供的功能一样,是很庞大的。 而通常情况下,我们仅仅需要少量的几个组件就足够了,但是我们却将庞大的组件库打包到我们的源码中,造成了不必要的开销。
    不过很多组件库已经提供了现成的解决方案,如Element出品的babel-plugin-component和AntDesign出品的babel-plugin-import 安装以上插件后,在.babelrc配置中或babel-loader的参数中进行设置,即可实现组件按需加载了。

  2. 单页应用的按需加载 现在很多前端项目都是通过单页应用的方式开发的,但是随着业务的不断扩展,会面临一个严峻的问题——首次加载的代码量会越来越多,影响用户的体验。
    通过import()语句来控制加载时机,webpack内置了对于import()的解析,会将import()中引入的模块作为一个新的入口在生成一个chunk。 当代码执行到import()语句时,会去加载Chunk对应生成的文件。import()会返回一个Promise对象,所以为了让浏览器支持,需要事先注入Promise polyfill

如何提高构建速度?
  1. 多入口情况下,使用CommonsChunkPlugin来提取公共代码
  2. 通过externals配置来提取常用库
  3. 利用DllPlugin和DllReferencePlugin预编译资源模块 通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。
  4. 使用Happypack 实现多线程加速编译
  5. 使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。 原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度
  6. 使用Tree-shaking和Scope Hoisting来剔除多余代码
转义出的文件过大怎么办?

更多资源

HTTP协议与安全

发表于 2019-05-06 | 分类于 http

HTTP

HTTP协议的主要特点
  • 简单快速(每个资源是固定的url)
  • 灵活(通过头部type会有不同结果)
  • 无连接(连接一次就会断掉)
  • 无状态(客户端和服务端)
HTTP报文的组成部分
  1. 请求报文
    • 请求行 (http方法 页面地址 http协议 http版本)
    • 请求头 (key val 值告诉服务端需要什么内容 注意类型)
    • 空行 (遇到空行时,下一个当着请求体)
    • 请求体
  2. 响应报文(状态行,响应头,空行,响应体)
HTTP 状态码
  • 1xx:指示信息,请求已接收,继续处理
  • 2xx 表示成功,请求已被成功接收
  • 3xx 表示需要进一步操作,重定向,完成请求必须进行进一步的操作
  • 4xx 表示浏览器方面出错,客户端错误,请求有语法错误或者请求无法实现
  • 5xx 表示服务器方面出错,服务端错误,服务器未能实现合法的请求
常见状态码
  • 200 客户端请求成功
  • 206 客户发送一个带有range(范围)头的GET请求,服务器完成它(用于视频)
  • 301 所请求的页面已经转移至新的url
  • 302 所请求的页面已经临时转移至新的url
  • 304 客户端有缓冲的文档并发出了一个条件性的请求,服务器告诉客户,原来缓冲的文档还可以继续使用
  • 400 客户端请求有语法错误,不能被服务器所理解
  • 401 请求未经授权,这个状态码必须和WWW-Authenticate报文域一起使用
  • 403 对被请求页面的访问被禁止
  • 404 请求资源不存在
  • 500 服务器发生不可预期的错误原来缓冲的文档还可以继续使用
  • 503 请求未完成,服务器临时过载或当机,一段时间后可能恢复正常
    想了解更多
HTTP 缓存 (缓存就是资源文件在浏览器中存在的备份或者副本,如:网上请求的图片,去存在本地磁盘)
  • 缓存分类
    • 强缓存(以下两个缓存都下发,以后者为准)
      • Expires Expires:THu,21 Jan 2017 23:39/;02 GMT(绝对时间,客服端和服务器时间不一致,比较的时候,是一浏览器时间,下发是服务器时间)
      • Cache-Control Cache-Control:max-age=3600(3600是相对时间)
    • 协商缓存(和服务器协商)
      • Last-Modified If-Modified-Since Last-Modified: Web, 26 Jan 2017 23:39/;02 GMT
      • Etag If-None-Match
    • 与浏览器缓存相关的http头有哪些,就是以上几个
  • 总结:
    • ETag 是通过对比浏览器和服务器资源的特征值(如MD5)来决定是否要发送文件内容,如果一样就只发送 304(not modified)
    • Expires 是设置过期时间(绝对时间),但是如果用户的本地时间错乱了,可能会有问题
    • CacheControl: max-age=3600 是设置过期时长(相对时间),跟本地时间无关
      详细了解 ETag、CacheControl、Expires 的异同
GET 和 POST 的区别
  • 一般理解
    1. GET在浏览器回退时是无害的,而POST会再次提交请求
    2. GET产生的URL地址可以被加入收藏栏,而POST不可以
    3. GET请求会被浏览器主动cache,而POST不会,除非手动设置
    4. GET请求只能进行url编码,而POST支持多种编码方式
    5. GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留
    6. GET请求在URL中传送的参数是有长度限制的,而POST么有
    7. 对参数的数据类型,GET只接受ASCII字符,而POST没有限制
    8. GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息
    9. GET参数通过URL传递,POST放在Request body中
  • 核心
    • 就一个区别:语义——GET 用于获取资源,POST 用于提交资源
      请参考
Cookie V.S. LocalStorage V.S. SessionStorage V.S. Session
  • Cookie V.S. LocalStorage
    • 主要区别是 Cookie 会被发送到服务器,而 LocalStorage 不会
    • Cookie 一般最大 4k,LocalStorage 可以用 5Mb 甚至 10Mb(各浏览器不同)
  • LocalStorage V.S. SessionStorage
    • LocalStorage 一般不会自动过期(除非用户手动清除),而 SessionStorage 在会话结束时过期(如关闭浏览器)
  • Cookie V.S. Session
    • Cookie 存在浏览器的文件里,Session 存在服务器的文件里
    • Session 是基于 Cookie 实现的,具体做法就是把 SessionID 存在 Cookie 里
XSS(Cross-Site Scripting):跨越脚本攻击
  • 原理
    • 不需要做任何的登录验证,核心原理向页面注入脚本
  • 造成 XSS 有几个要点:
    1. 恶意用户可以提交内容
    2. 提交的内容可以显示在另一个用户的页面上
    3. 这些内容未经过滤,直接运行在另一个用户的页面上
  • XSS 的成因以及防御措施
    1. 后台模板问题:要后台输出的时候,将可疑的符号 < 符号变成 < (HTML实体)就行。
    2. 前端代码问题:就是不要自己拼 HTML,尽量使用 text 方法。如果一定要使用 HTML,就把可疑符号变成 HTML 实体
CSRF(Cross Site Request Forgery):跨站请求伪造
  • 原理
    • 攻击者构造网站后台某个功能接口的请求地址,诱导用户去点击或者用特殊方法让该请求地址自动加载。用户在登录状态下这个请求被服务端接收后会被误以为是用户合法的操作。对于 GET 形式的接口地址可轻易被攻击,对于 POST 形式的接口地址也不是百分百安全,攻击者可诱导用户进入带 Form 表单可用POST方式提交参数的页面。
  • 使用anti-csrf-token的方案。 具体方案如下:
    1. 服务端在收到路由请求时,生成一个随机数,在渲染请求页面时把随机数埋入页面(一般埋入 form 表单内,)
    2. 服务端设置setCookie,把该随机数作为cookie或者session种入用户浏览器
    3. 当用户发送 GET 或者 POST 请求时带上_csrf_token参数(对于 Form 表单直接提交即可,因为会自动把当前表单内所有的 input 提交给后台,包括_csrf_token)
    4. 后台在接受到请求后解析请求的cookie获取_csrf_token的值,然后和用户请求提交的_csrf_token做个比较,如果相等表示请求是合法的。
  • 总结:
    1. CSRF
      • (网站中某个接口存在漏洞,用户在这个注册网站确实登录过)
      • 防御措施
        • Token验证:用户请求之后,会返回cookie信息,并且服务器向本地存储Token
        • Referer验证: 页面来源,服务器判断页面来源是否是当前站点下的
    2. xss: 向xss注入的js不可执行
  • 两者区别
    • xss向页面注入js运行,js函数体去做想做的
    • CSRF是利用本身的漏洞,帮你去执行相应的接口
  • 推荐查阅资料
    1. 文章 xsrf
    2. 文章 xss
    3. 视频 csrf xss

跨域

发表于 2019-05-05 | 分类于 JS

跨域的几种解决方案

JSONP

来龙去脉啦

  • 同源策略(Same origin Policy):浏览器出于安全方面的考虑,只允许与同域下的接口交互。
  • 同域指的是?
    • 同协议:都是http或者https
    • 同域名:都是http://baidu.com/a 和http://baidu.com/b
    • 同端口:都是80端口
  • JSONP的原理
    1. JSONP是通过 script 标签加载数据的方式去获取数据当做 JS 代码来执行
    2. 提前在页面上声明一个函数,函数名通过接口传参的方式传给后台,后台解析到函数名后在原始数据上「包裹」这个函数名,发送给前端。换句话说,JSONP 需要对应接口的后端的配合才能实现
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      // 简单的实现原理就是
      <script src="http://www.abc.com/?data=name&callback=jsonp" charset="utf-8"></script>
      <script type="text/javascript">
      jsonp showData(res) {
      console.log(res);
      }
      </script>
      // 借用大佬封装的代码
      function jsonp(setting) {
      setting.data = setting.data || {};
      setting.key = setting.key || 'callback';
      setting.callback = setting.callback || function() {};
      setting.data[setting.key] = '__onGetData__';

      window.__onGetData__ = function(data) {
      setting.callback (data);
      }
      var script = document.createElement('script');
      var query = [];
      for(var key in setting.data) {
      query.push( key + '=' + encodeURIComponent(setting.data[key]));
      }
      script.src = setting.url + '?' + query.join('&');
      document.head.appendChild(script);
      document.head.removeChild(script);
      }
      jsonp({
      url: 'http://api.baidu.com/weather.php',
      callback: function(res) {
      console.log(res)
      }
      })
      jsonp({
      url: 'http://photo.sina.cn/aj/index',
      key: 'jsoncallback',
      data: {
      page: 1,
      cate: 'recommend'
      },
      callback: function(res) {
      console.log(res)
      }
      })
CORS

阮一峰教程

  • CORS需要浏览器和服务器同时支持
  • 实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
    在header信息中添加Origin
    CORS与JSONP的使用目的相同,但是比JSONP更强大。
    JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
闭包(Closure)

还是老师厉害

  • 「函数」和「函数内部能访问到的变量」(也叫环境)的总和,就是一个闭包
  • 平时我们的认知的闭包:「需要函数套函数,然后 return 一个函数的呀!」
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function foo() {
    var local = 1;
    function bar() {
    local++;
    return local;
    }
    return bar; // return window.bar = bar;
    }
    var func = foo();
    func();
为什么要函数套函数呢?
  • 因为需要局部变量,所以才把 local 放在一个函数里,如果不把 local 放在一个函数里,local 就是一个全局变量了,达不到使用闭包的目的——隐藏变量
    闭包的原文是 Closure,跟「包」没有任何关系。
    所以函数套函数只是为了造出一个局部变量,跟闭包无关。
为什么要 return bar 呢?
  • 因为如果不 return,你就无法使用这个闭包。把 return bar 改成 window.bar = bar 也是一样的,只要让外面可以访问到这个 bar 函数就行了。
  • 所以 return bar 只是为了 bar 能被使用,也跟闭包无关。
闭包的作用
  • 闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」
  • 暴露一个访问器(函数),让别人可以「间接访问」
你们闭包会造成内存泄漏?
  • 内存泄露是指你用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来
  • 闭包里面的变量明明就是我们需要的变量,凭什么说是内存泄露?
    • 因为 IE。IE 有 bug,IE 在我们使用完闭包之后,依然回收不了闭包里面引用的变量
    • 这是 IE 的问题,不是闭包的问题。参见司徒正美的这篇文章
立即执行函数
  • 声明一个匿名函数
  • 马上调用这个匿名函数
    1
    (function(){alert('我是匿名函数')})()
那么为什么还要用另一对括号把匿名函数包起来呢?
  • 为了兼容 JS 的语法
  • 不加的话,览器会报语法错误。想要通过浏览器的语法检查,必须加点小东西,比如下面几种都OK👌
    1
    2
    3
    4
    5
    6
    7
    8
    (function(){alert('我是匿名函数')} ()) // 用括号把整个表达式包起来
    (function(){alert('我是匿名函数')}) () //用括号把函数包起来
    !function(){alert('我是匿名函数')}() // 求反,我们不在意值是多少,只想通过语法检查
    +function(){alert('我是匿名函数')}()
    -function(){alert('我是匿名函数')}()
    ~function(){alert('我是匿名函数')}()
    void function(){alert('我是匿名函数')}()
    new function(){alert('我是匿名函数')}()
立即执行函数有什么用?
  • 创建一个独立的作用域。使这个作用域里面的变量,外面访问不到(即避免「变量污染」)
  • 以下经典案列
    1
    2
    3
    4
    5
    6
    var liList = ul.getElementsByTagName('li')
    for(var i = 0; i < 6; i++) {
    liList[i].onclick = function() {
    alert(i); // 为什么 alert 出来的总是 6,而不是 0、1、2、3、4、5
    }
    }

i 是贯穿整个作用域的,而不是给每个 li 分配了一个 i,用户一定是在for运行完之后才点击,此时都i为6
用立即执行函数给每个 li 创造一个独立作用域即可(当然还有其他办法: 如 let):
还原效果,click me!

1
2
3
4
5
6
7
8
9
var ul = document.getElementsByTagName('ul')[0]
var liList = ul.getElementsByTagName('li');
for(var i = 0; i < 6; i++) {
!function(ii) {
liList[ii].onclick = function() {
alert(ii); // 0、1、2、3、4、5
}
}(i)
}

在立即执行函数执行的时候,i 的值被赋值给 ii,此后 ii 的值一直不变。
i 的值从 0 变化到 5,对应 6 个立即执行函数,这 6 个立即执行函数里面的 ii 「分别」是 0、1、2、3、4、5。

this

发表于 2019-05-05 | 分类于 JS

this到底指向谁呢?

惊喜在这里

看自己平时积累了
  1. fn() this => window/global
  2. obj.fn() this => obj
  3. fn.call(xx) this => xx
  4. fn.apply(xx) this => xx
  5. fn.bind(xx) this => xx
  6. new Fn() this => 新创建的对象
  7. fn = ()=> {} this => 外面的 this
看调用
  • 函数调用
  • JS(ES5)里面有三种函数调用形式
    • func(p1, p2)
    • obj.child.method(p1, p2)
    • func.call(context, p1, p2)
  • 一般我们都比较倾向于使用前两种,第三种不知道什么鬼东西呀
    其他两种都是语法糖,可以等价地变为 call 形式:
    1
    2
    func(p1, p2) 等价于 func.call(undefined, p1, p2)
    obj.child.method(p1, p2) 等价于 obj.child.method.call(obj.child, p1, p2)

函数调用只有一种形式 : func.call(context, p1, p2), context就是this

看看下面的举例
1
2
3
4
5
6
function func() {
console.log(this)
}
func();
// 等价转化一下
func.call(undefined) // 可简写为 func.call(), 结果就是window

是不是疑问应该是undefined呢?可不能忘记浏览器里有一条规则:

  • 如果你传的context是 null 或者 undefined,那window 对象就是默认的 context(但严格模式下(’use strict’)默认 context 是 undefined)

如果你不想this是window,好说,那就这样吧: func.call(obj) // 那么里面的 this 就是 obj 对象了

1
2
3
4
5
6
var obj = {
foo: function() {
console.log(this);
}
}
obj.foo(); // 等价转化为obj.foo.call(obj) ,结果就是obj

看看下面的代码,是不是so easy! :(

1
2
3
4
5
6
7
8
9
10
var obj = {
foo: function() {
console.log(this);
}
}
var bar = obj.foo;
obj.foo(); // 转换为 obj.foo.call(obj),this 就是 obj
bar()
// 转换为 bar.call(), 没有传 context, 所以 this 就是 undefined
// 最后浏览器给你一个默认的 this —— window 对象

类似 [] 的,this怎么指向呢
1
2
3
4
5
function fn () { 
console.log(this);
}
var arr = [fn, fn2];
arr[0]() // arr 转换为 arr.0() arr.0.call(arr) (语法是有问题的,这样推理木有问题呀)
总结
  • this 就是你 call 一个函数时,传入的第一个参数
  • 如果你的函数调用形式不是 call 形式,请[转换代码]将其转换为 call 形式。
问题又来了,Event Handler 中的 this 指什么呢?

不知道ddEventListener 的源代码,怎么办呢?

1
2
3
btn.addEventListener('click' ,function handler(){
console.log(this) // 请问这里的 this 是什么
})

MDN 对this解释

  • 通常来说this的值是触发事件的元素的引用,这种特性在多个相似的元素使用同一个通用事件监听器时非常让人满意。
  • 当使用 addEventListener() 为一个元素注册事件的时候,句柄里的 this 值是该元素的引用。其与传递给句柄的 event 参数的 currentTarget 属性的值一样。
    所以你可以想浏览器的源码是这样写的:
    1
    2
    3
    // 当事件被触发时
    handler.call(event.currentTarget, event)
    // 那么 this 是什么不言而喻
jQuery Event Handler 中的 this

下面代码中的 this 是什么呢:

1
2
3
$ul.on('click', 'li' , function(){
console.log(this)
})

jQuery 文档是这样写的:

  • 当jQuery的调用处理程序时,this关键字指向的是当前正在执行事件的元素。对于直接事件而言,this 代表绑定事件的元素。
  • 对于代理事件而言,this 则代表了与 selector 相匹配的元素。(注意,如果事件是从后代元素冒泡上来的话,那么 this 就有可能不等于 event.target。)若要使用 jQuery 的相关方法,可以根据当前元素创建一个 jQuery 对象,即使用 $(this)。
总结一下如何确定 this 的值
  1. 看源码中对应的函数是怎么被 call 的(这是最靠谱的办法)
  2. 看文档
  3. console.log(this)
  4. 不要瞎猜,你猜不到的:(
如何强制指定 this 的值,call/apply 即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function handlerWrapper(event) {
function handler() {
console.log(this); // 请问这里的 this 是什么
}
handler.call({name: 'xlc'}, event)
}
btn.addEventListener('click', handlerWrapper);
// 也可以直接使用bind
function handler() {
console.log(this); // 请问这里的 this 是什么
}
var handlerWrapper = handler.bind({name: 'xlc'})
btn.addEventListener('click', handlerWrapper);
// 上面代码可以简化为
btn.addEventListener('click', function() {
console.log(this); // 请问这里的 this 是什么
}.bind({name: 'xlc'}))

ES6

发表于 2019-05-05 | 分类于 JS

ES6 语法及应用

es6学习可参照

作用域
  • 块级作用域: 由一对大括号{}界定
  • 块级变量 let:声明一个块级作用域的本地变量,可选的将其初始化为一个值
  • 块级常量 const: 常量的值不能通过重新赋值来改变,并且不能重新声明( 是否指向同一个内存地址 )
let、const与var的区别
  • var
    • 可以重复定义
    • 可以随意更改
    • 没有块级作用域
  • let与const
    • 在同一个块级作用域中,不允许重复定义
    • const定义的变量不允许二次修改
    • let和const定义的变量会形成块级作用域
    • 它们定义的变量不存在变量提升,以及存在暂时性死区
箭头函数
  • sum = (a, b) => a + b
  • nums.forEach( v => { console.log(v) })
  • 强大之处 this指向不会更改
参数处理
  • 默认参数值: 允许在没有值或undefined被传入时使用默认形参

    1
    2
    3
    4
    5
    function multiply(a, b = 1) {
    return a * b;
    }
    console.log(multiply(5, 2)); // 10
    console.log(multiply(5)); // 5
  • 剩余参数: 允许将一个不定数量的参数(theArgs)表示为一个数组

    1
    2
    3
    4
    5
    6
    7
    function sum(...theArgs) {
    return theArgs.reduce((previous, current) => {
    return previous + current;
    });
    }
    console.log(sum(1, 2, 3)); // 6
    console.log(sum(1, 2, 3, 4)); // 10
  • 展开运算符: 函数调用时,将表达式(数组/对象-简单的数据类型)做对应语法的展开

    1
    2
    3
    4
    5
    6
    function sum(x, y, z) {
    return x + y + z;
    }
    const numbers = [1, 2, 3];
    console.log(sum(...numbers)); // 6
    console.log(sum.apply(null, numbers)); // 6
模板字面量:允许嵌入表达式的字符串字面量, 反引号包裹字符串
  • 多行字符串 字符串内容
  • 字符串插值 ${字符串变量}
  • 带标签的模板字面量
  • 原始字符串
解构赋值: 将值从数组,或属性从对象,提取到不同的变量中
  • 数组匹配 [ b, a ] = [ a, b ]
  • 对象匹配 let obj = {a: 1, b: 23}; let { a, b, c } = obj;
  • 参数匹配 function fun({ name: n, val: v }) {}
模块
  • 导入import :导入由另一个模块导出的绑定
    • import {foo, bar as b} from ‘/xxx.js’;
    • 常使用导入多个接口,或者重命名接口名称都很实用
  • 导出export
  • 默认导出 export default: export default function(){} / export default class {}
类
  • 使用 extends 实现继承
  • 重写构造器
  • super 关键字
是否有class关键字实现继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Animal(color){
this.color = color
}
Animal.prototype.move = function() {}
function Dog(color, name) {
Animal.call(this, color); // 或者 Animal.apply(this, arguments)
this.name = name;
}
// 下面三行实现 Dog.prototype.__proto__ = Animal.prototype
function temp() {}
temp.prototye = Animal.prototype;
Dog.prototype = new temp();
Dog.prototype.constuctor = Dog;
Dog.prototype.say = function(){ console.log('汪')}
var dog = new Dog('白色','咪汪');
1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal {
constructor(color) {
this.color = color
}
move(){}
}
class Dog extends Animal {
constructor(color, name) {
super(color)
this.name = name
}
say() {}
}
原有内置对象 API 增强
  • 常用
    • Object.assign:将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。Object.assign(target, …source);
    • Array.from: 从一个类似数组或可迭代对象中创建一个新的数组实例。Array.from(arrayLike[, mapFn[, thisArg]])
Promise
Promise: 一个异步操作的最终状态(完成或失败),以及该异步操作的结果值
* pending: 初始状态,既不是成功,也不是失败状态。
* fulfilled: 操作成功完成。
* rejected: 操作失败。
1
2
3
4
5
6
7
function fn(){
return new Promise((resolve, reject)=>{
成功时调用 resolve(数据)
失败时调用 reject(错误)
})
}
fn().then(success, fail).then(success2, fail2)
Promise.all
  • promise1和promise2都成功,才会调用success1
  • promise1和promise2有一个失败,都会调用fail1
    1
    Promise.all([promise1, promise2]).then(success1, fail1)
Promise.race
  • promise1和promise2只要有一个成功就会调用success1
  • promise1和promise2都失败,才会调用fail1
    1
    Promise.race([promise1, promise2]).then(success1, fail1)
手写一个 Promise 参考
async 与 await
  • 与Promise相关,
  • 如果是异步函数, 它的语法和结构会更像是标准的同步函数
  1. promise实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function fn() {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    let n = parseInt(Math.random() * 6 + 1, 10); // [0, 5)
    resolve(n);
    }, 3000);
    });
    }
    fn().then(
    (n) => {
    console.log('成功返回骰子数' + n);
    },
    (err) => {
    console.log('失败返回!');
    }
    );
  2. await的简单使用

    1
    2
    3
    4
    5
    6
    async function test() {
    // test的执行,或者n的赋值一定是在3s之后才执行
    let n = await fn();
    console.log(n);
    }
    test();
  3. await语法中error错误处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    function fn(params) {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    let n = parseInt(Math.random() * 6 + 1, 10); // [0, 6)
    if (n > 3) {
    if (params === ‘大’) {
    resolve(n);
    } else {
    reject(n);
    }
    } else {
    if (params === ‘小’) {
    resolve(n);
    } else {
    reject(n);
    }
    }
    }, 3000);
    });
    }
    async function test() {
    try {
    let n = await fn('大');
    console.log('猜中了' + n);
    } catch (error) {
    console.log('输了' + error);
    }
    }
    test();
  • 处理错误2(更优雅 try、catch搞太多代码难看些)

    • 使用库async-to-js await-to-js
    • 库async-to-js的to函数返回一个Promise且值是一个数组,数组之中有两个元素,如果索引为0的元素不为空值,说明该请求报错,如果索引0的元素为空值说明该请求没有报错,也就是成功
    • ?错误在0的位置(错误优先的原则)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      const handleLogin = async () => {
      const [resErr, res] = await to(request('/xx/xx', {
      usename: '',
      password: ''
      }))
      if (resErr) {
      // fail do somthing
      return
      }
      const [userErr, info] = await to(request('/xx/xx', {
      id: res.id
      }))
      if (userErr) {
      // fail do somthing
      return
      }
      this.userInfo = info
      }
  • 处理async/await中的错误用try/catch是有什么不好的地方

    • 有时候需要在上一条 await 语句 reject 的时候,不中断 async 函数的执行,继续执行后续的 await,try…catch 捕获的粒度没办法做到很细,除非给每个 await 语句都加上 try…catch,这样太麻烦,所以用了下这个库,这种风格实际上是参考了 Golang 中的异常处理机制,因为 Golang 里面没有 try…catch,抛一个 panic 直接中断程序执行了
  • ?多个await呢?而且每个await的错误处理都不同的话,那try catch咋做
    • 写一个工具函数来await执行异步方法,在它内部通过try-catch处理异常,外部通过返回值来获取异常
  1. 同时调用fn函数,Promise处理

    1
    2
    3
    4
    5
    6
    7
    8
    Promise.all([fn('大'), fn('大')]).then(
    res => {
    console.log(res);
    },
    error => {
    console.log(error);
    }
    );
  2. 同时调用fn函数,await处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    async function test() {
    // test的执行,或者n的赋值一定是在3s之后才执行
    try {
    let n = await Promise.all([fn('大'), fn('大')]);
    console.log('猜中了' + n);
    } catch (error) {
    console.log('输了' + error);
    }
    }
    test();

    async与await返回的都是Promise,async与await可理解为Promise的语法糖
    想了解更多,看阮一峰-es6教程

实现垂直居中、flex

发表于 2019-05-04 | 分类于 CSS

实现垂直居中

  • 如果父元素.parent的height未知,只需要padding: 10px 0; 就能将子元素.child垂直居中
  • 如果父元素.parent的height已知,就很难把子元素.child居中
以下是垂直居中的方法
  1. table布局实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    .parent {
    height: 500px;
    border: 1px solid #ff9400;
    }

    .child {
    width: 300px;
    border: 1px solid #ff0000;
    }
    <table class="parent">
    <tr>
    <td class="child">居中元素</td>
    </tr>
    </table>
  2. 给父元素添加伪元素(100% 高度的 afrer before 加上 inline block)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    .parent {
    height: 500px;
    border: 1px solid #ff9400;
    text-align: center;
    }

    .child {
    width: 300px;
    display: inline-block;
    vertical-align: middle;
    border: 1px solid #ff0000;
    }

    .parent:after,
    .parent:before {
    content: '';
    display: inline-block;
    height: 100%;
    vertical-align: middle;
    }
    <div class="parent">
    <div class="child">垂直居中元素</div>
    </div>
  3. table属性应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    .parent {
    display: table;
    height: 500px;
    border: 1px solid #ff9400;
    text-align: center;
    }

    .parent > div {
    width: 300px;
    display: table-cell;
    vertical-align: middle;
    }

    .child {
    border: 1px solid #ff0000;
    }
    <div class="parent">
    <div>
    <div class="child">垂直居中元素</div>
    </div>
    </div>
  4. position定位实现,margin设置负值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    .parent {
    height: 500px;
    position: relative;
    border: 1px solid #ff9400;
    }

    .child {
    width: 300px;
    height: 100px;
    position: absolute;
    left: 50%;
    top: 50%;
    margin-left: -150px;
    margin-top: -50px;
    border: 1px solid #ff0000;
    }

    <div class="parent">
    <div class="child">垂直居中元素</div>
    </div>
  5. 也是定位,只是应用了css3 的 translate

    1
    2
    3
    4
    5
    6
    7
    .child {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
    border: 1px solid #ff0000;
    }
  6. 同样是定位,不过巧妙的用了margin: auto

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    .child {
    width: 300px;
    height: 50px;
    position: absolute;
    margin: auto;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    border: 1px solid #ff0000;
    }
  7. 是不是终于到大家常用的flex啦

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    .parent {
    height: 500px;
    display: flex;
    justify-content: center;
    align-items: center;
    border: 1px solid #ff9400;
    }

    .child {
    width: 300px;
    border: 1px solid #ff0000;
    }

想快速看到效果:自己动手吧:)
能不写 height 就千万别写 height吧
欢迎👏贡献更多方法

flex

  • flex的属性还是需要多实践操作,才会知道具体效果
  • 不妨看看阮一峰老师的教程
    flex-语法
    flex-实例
1…567
yongfeng.peng

yongfeng.peng

(女英

67 日志
13 分类
11 标签
© 2022 yongfeng.peng