-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
182 lines (182 loc) · 274 KB
/
search.xml
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[高效JavaScript 68诫]]></title>
<url>%2F2017%2F12%2F17%2F%E9%AB%98%E6%95%88JavaScript-68%E8%AF%AB%2F</url>
<content type="text"><![CDATA[将《编写高质量JavaScript代码的68个有效方法》书中提到的68个方法分享给大家。 让自己习惯JavaScript 了解你使用的JavaScript版本 理解JavaScript的浮点数 当心隐式的强制转换 原始类型优于封装对象 避免对混合类型使用==运算符 了解分号插入的局限 视字符串为16位的代码单元序列 变量作用域 尽量少用全局对象 始终声明局部变量 避免使用with 熟练掌握闭包 理解变量声明提升 使用立即调用的函数表达式创建局部函数 当心命名函数表达式笨拙的作用域 当心局部块函数声明笨拙的作用域 避免使用eval创建局部变量 间接调用eval函数优于直接调用 使用函数 理解函数调用、方法调用及构造函数 熟练掌握高阶函数 使用call方法自定义接收者来调用函数 使用apply方法通过不同数量的参数调用函数 使用arguments创建可变参数的函数 永远不要修改arguments对象 使用变量保存arguments的引用 使用bind方法提取具有确定接收者的方法 使用bind方法实现函数柯里化 使用闭包而不是字符串来封装代码 不要信赖函数对象的toString方法 避免使用非标准的栈检查属性 对象和原型 理解prototype、getPrototypeOf和__proto__之间的不同 使用Object.getPrototypeOf函数而不要使用__proto__属性 始终不要修改__proto__属性 使构造函数与new操作符无关 在原型中存储方法 使用闭包存储私有数据 只将实例状态存储在实例对象中 认识到this变量的隐式绑定问题 在子类的构造函数中调用父类的构造函数 不要重用父类的属性名 避免继承标准类 将原型视为实现细节 避免使用轻率的猴子补丁(monkey-patching) 数组和字典 使用Object的直接实例构造轻量级的字典 使用null原型以防止原型污染 使用数组而不要使用字典存储有序集合 使用hasOwnProperty方法以避免原型污染 使用数组而不要使用字典来存储有序集合 绝不要在Object.prototype中增加可枚举的属性 避免在枚举期间修改对象 数组迭代要优先使用for循环而不是for...in循环 迭代方法由于循环 在类数组对象上复用通用的数组方法 数组字面量优于数组构造函数 库和API设计 保持一致的约定 将undefined看做“没有值” 接收关键字参数的选项对象 避免不必要的状态 使用结构类型设计灵活的接口 区分数组对象和类数组对象 避免过度的强制转换 支持方法链 并发 不要阻塞I/O事件队列 在异步序列中使用嵌套或命名的回调函数 当心丢弃错误 对异步循环使用递归 不要在计算时阻塞事件队列 使用计数器来执行并行操作 绝不要同步的调用异步的回调函数 使用promise模式清洁异步逻辑]]></content>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[2017年书单]]></title>
<url>%2F2017%2F12%2F17%2F2017%E5%B9%B4%E4%B9%A6%E5%8D%95%2F</url>
<content type="text"><![CDATA[2017年书单(15/12)去年计划读12本书,很显然没有完成任务。2017年继续“12本书计划”。 “人丑就该多读书”其实只是前半句,后半句是“不丑多读书也没坏处”。 《你不知道的JavaScript·上卷》 于 #2017.3.16# 读完 《万物简史》 于 #2017.3.17# 读完 《我脑袋里的怪东西》 于 #2017.4.12# 读完 《JavaScript高级程序设计》 于 #2017.4.26# 读完 《你不知道的JavaScript·中卷》 于 #2017.4.29# 读完 《二手时间》 于 #2017.5.11# 读完 《CSS设计指南》 于 #2017.5.14# 读完 《JavaScript语言精粹》 于 #2017.5.26# 读完 《拖延心理学》 于 #2017.6.1# 读完 《CSS揭秘》 于 #2017.6.25# 读完 《极简宇宙史》 于 #2017.6.26# 读完 《巨人的陨落》 于 #2017.7.22# 读完 《Node即学即用》 于 #2017.8.28# 读完 《灰犀牛》 于 #2017.9.25# 读完 《编写高质量JavaScript代码的68个有效方法》 于 #2017.12.17# 读完 持续更新…]]></content>
<tags>
<tag>阅读</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Promise深入理解]]></title>
<url>%2F2017%2F12%2F07%2FPromise%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%2F</url>
<content type="text"><![CDATA[什么是PromisePromise的核心理念是一个异步操作的结果,Promise包含两部分 [[PromiseStates]] [[PromiseValue]] Promise状态三种可能: pending: 悬而未决 resolved: 决定 rejected: 拒绝 异步性123456789const p = new Promise((resolve, reject) => { resolve('success');});p.then((value) => { console.log(value);});console.log('Called first ?'); Promise的then()具有异步性,当执行到.then()部分,这部分会自动进入到Promise的异步事件队列,不会阻塞同步代码的执行,所以Called first?先输出。 输出结果 12Called first ?success 立即执行性12345678910const p = new Promise((resolve, reject) => { console.log('Create a promise'); resolve('success');});p.then((value) => { console.log(value);});console.log('After new Promise'); 从Promise的异步性,我们可以推断出,After new Promise,会先于then()方法中的输出。同时Promise的立即执行性,定义了promise定义的同时就会立即执行,并不依赖于then()的调用。而且与函数明显不同,函数需要主动调用才会执行。 输出结果 123Create a promiseAfter new Promisesuccess Promise的状态1234567891011121314151617181920212223242526272829303132333435363738const p1 = new Promise((resolve, reject) => { resolve(1);});const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(2); }, 500);});const p3 = new Promise((resolve, reject) => { setTimeout(() => { reject(3); }, 500);});console.log(p1);console.log(p2);console.log(p3);setTimeout(() => { console.log('p2-setTimeout:', p2);}, 501);setTimeout(() => { console.log('p3-setTimeout:', p3);}, 501);p1.then((value) => { console.log('p1-then:', value);});p2.then((value) => { console.log('p2-then:', value);});p3.catch((err) => { console.log('p3-catch', err);}); 当Promise创建完成时,处于 pending 状态;当Promise执行了resolve方法,Promise对象的状态会变成 resolved 状态;当Promise执行了reject方法,Promise对象的状态会变成 rejected 状态; 先输出 123Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} 然后输出 1p1-then: 1 500ms之后,p2和p3的Promise状态被执行,then被触发,输出:12p2-then: 2p3-catch 3 最后会输出: 12p2-setTimeout: Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 2}p3-setTimeout: Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 3} 状态不可逆性12345678910111213141516171819const p1 = new Promise((resolve, reject) => { resolve('p1 success 1'); resolve('p1 success 2');});const p2 = new Promise((resolve, reject) => { resolve('p2 success'); reject('p2 reject');});p1.then((value) => { console.log(value);});p2.then((value) => { console.log(value);}, (err) => { console.log(err);}); Promise一旦变成resolved或是rejected,这个状态就不能再次变化,这就是Promise的不可逆性。 输出 12p1 success 1p2 success 链式调用1234567891011121314151617181920const p = new Promise(((resolve, reject) => { resolve(1);}));p.then((value) => { console.log(value); return value * 2;}).then((value) => { console.log(value);}).then((value) => { console.log(value); return Promise.resolve('resolve');}).then((value) => { console.log(value); return Promise.reject('reject');}).then((value) => { console.log('resolve: ', value);}, (err) => { console.log('reject: ', err);}); Jquery对象链式调用是执行jquery方法之后,会继续返回jquery对象;类似这个原理,Promise对象的then方法会返回一个新的Promise对象,这样就可以继续调用then方法。同样then方法中的两个参数还是fulfilled对象和rejected对象。 当return一个值或者Promise.resolve()时,状态为 resolved当throw一个异常或者return Promise.reject(),状态为 rejected 注: 当then()方法中没有return时,会默认返回undefined,状态为 resolved 输出 1234512undefinedresolvereject: reject Promise中的异常处理12345678910111213141516171819202122232425262728293031323334const p1 = new Promise((resolve, reject) => { foo.bar(); resolve(1);});p1.then((value) => { console.log('p1 then value: ', value);}, (err) => { console.log('p1 then err: ', err);}).then((value) => { console.log('p1 then then value: ', value);}, (err) => { console.log('p1 then then err: ', err);});var p2 = new Promise((resolve, reject) => { resolve(2);});p2.then((value) => { console.log('p2 then value: ', value); foo.bar();}, (err) => { console.log('p2 then err: ', err);}).then((value) => { console.log('p2 then then value: ', value);}, (err) => { console.log('p2 then then err: ', err); return 1;}).then((value) => { console.log('p2 then then then value: ', value);}, (err) => { console.log('p2 then then then err: ', err);}); Promise中的异常会交给then方法中的第二个回调函数处理,一旦处理完成,会继续返回一个Promise对象给后续then方法。 可以看到输出是p1和p2交替输出的,这个并不一定是交替输出,取决于执行情况,也可能是p2先输出。 输出 12345p1 then err: ReferenceError: foo is not definedp2 then value: 2p1 then then value: undefinedp2 then then err: ReferenceError: foo is not definedp2 then then then value: 1 Promise.resolve()Promise.resolve()语法: 1Promise.resolve(value); value: 用来解析待返回promise对象的参数,既可以是一个promise对象,也可以是一个thenable(即带有then方法)。 12345678910111213141516171819202122232425262728293031const p1 = Promise.resolve(1);const p2 = Promise.resolve(p1);const p3 = new Promise((resolve, reject) => { resolve(1);});const p4 = new Promise((resolve, reject) => { resolve(p1);});// console.log(p1);// console.log(p2);// console.log(p3);// console.log(p4);console.log(p1 === p2);console.log(p1 === p3);console.log(p1 === p4);console.log(p3 === p4);p4.then((value) => { console.log('p4 = ', value);});p2.then((value) => { console.log('p2 = ', value);});p1.then((value) => { console.log('p1 = ', value);}) p1: 接收了一个普通值1,所以会返回一个resolved状态的Promise对象,并且值为1。p2: 接收了一个promise对象p1,会直接返回这个promise对象。p3和p4: 通过new方式创建了一个新的promise对象。 所以,p1 === p2,p3和p4都是创建的新对象,所以自身和其他三个对象都不相等。 输出1234truefalsefalsefalse 但是后三个输出是: 123p2 = 1p1 = 1p4 = 1 很有意思的是,明明是p4先执行的then方法,但是却是后输出的。 在定义完4个promise对象时,状态分别为:1234Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 1}Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} 很明显,p4是pending状态,与其他三个不同,因为p4的resolve中接收的参数是一个promise对象p1,resolve会对p1进行“拆箱”操作,这个过程是异步的。 注:把基本数据类型转换为对应的引用类型的操作称为装箱,把引用类型转换为基本的数据类型称为拆箱。 resolve() & reject() 的区别1234567891011121314151617181920212223242526272829const p1 = new Promise((resolve, reject) => { resolve(Promise.resolve('resolve'));});const p2 = new Promise((resolve, reject) => { resolve(Promise.reject('reject'));});const p3 = new Promise((resolve, reject) => { reject(Promise.resolve('resolve'));});p1.then((value) => { console.log('p1-resolve:', value);}, (err) => { console.log('p1-reject:', err);});p2.then((value) => { console.log('p2-resolve:', value);}, (err) => { console.log('p2-reject:', err);});p3.then((value) => { console.log('p3-resolve:', value);}, (err) => { console.log('p3-reject:', err);}); resolve方法和reject方法除了在状态上有区别,处理方式上也有区别,resolve方法上面提到了会对promise对象“拆箱”,但是reject方法不会。 p3没有“拆箱”操作,所以会最先输出,直接调用reject方法,输出Promise.resolve('resolve')对象p1会“拆箱”得到Promise.resolve('resolve')这个promise对象的状态和值,调用resolve方法。p2会“拆箱”得到Promise.reject('reject')这个promise对象的状态和值,因为得到的状态是rejected,所以会调用reject方法。 输出 123p3-reject: Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "resolve"}p1-resolve: resolvep2-reject: reject all() & race() & then() 区别Promise.all()语法: 1Promise.all(iterable); Promise.race()语法: 1Promise.race(iterable) iterable: 可迭代对象,例如一个数组。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546let timerPromisefy = (delay) => { return new Promise((resolve) => { setTimeout(() => { resolve(delay); }, delay); });}let timerPromisefyReject = (delay) => { return new Promise((resolve, reject) => { setTimeout(() => { reject(delay); }, delay); });}console.time('Promise all');Promise.all([ timerPromisefy(1), timerPromisefy(7), timerPromisefy(10), timerPromisefy(9)]).then((value) => { console.timeEnd('Promise all');});console.time('Promise then');timerPromisefy(1).then(() => { return timerPromisefy(7)}).then(() => { return timerPromisefy(10)}).then(() => { return timerPromisefy(9)}).then(() => { console.timeEnd('Promise then')});console.time('Promise race');Promise.race([ timerPromisefy(1), timerPromisefy(7), timerPromisefy(10), timerPromisefy(9)]).then(value => { console.timeEnd('Promise race');}); Promise.all()方法返回一个Promise,当iterable参数中的 promise 并行执行,当所有 promise 都已经 resolve 了,返回 resolved 状态。当传递的 promise 包含一个 reject ,则返回 rejected 状态。如果Promise.all()返回 resolved , 那么执行时间取决于执行最最慢的那个 promise;如果Promise.all()返回 rejected , 执行时间取决于第一个返回 rejected 的执行时间。 Promise.race()方法返回一个Promise,当iterable参数中只要有一个 promise 状态被判定了,那么就返回该状态。所以Promise.race()的执行时间取决于执行最快的那个 promise。 Promise.then()方法的执行时间,是每个链式调用总时间之和。 输出123Promise race: 2.3232421875msPromise all: 3.675048828125msPromise then: 31.32373046875ms]]></content>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CSS单位全解]]></title>
<url>%2F2017%2F11%2F24%2FCSS%E5%8D%95%E4%BD%8D%E5%85%A8%E8%A7%A3%2F</url>
<content type="text"><![CDATA[对于css单位的认识对于网页布局的单位,只知道px是仅仅不够的,还需要知道其他几个比较常见的单位,特别是css3推出的一些新的度量单位。 emem是一种相对单位,它相对于父元素的字体大小。 em常用于存在缩放需求时使用,比如在多行文本段落中,如果行高设置为line-height: 18px,如果文字的大小因为缩放发生改变,行高值是不会随之改变的,一直都是18px,如果将行高设置成一种相对值line-height: 1.2em,那么此时的行高值就会随着字体的大小改变而变化。 因为em是相对于父元素的字体大小,所以该单位存在明显的嵌套层级关系,浏览器默认的字体大小是16px,因此1em = 16px,需要注意的是chrome浏览器能够显示的最小字体是12px,当字体大小设置小于12px字体将按照12px显示。 See the Pen em by YeaseonZhang (@YeaseonZhang) on CodePen. remrem(root em)是一种相对单位,和em不同点是rem相对于根元素html的字体大小。 利用这个特性,我们常常使用rem单位进行移动端页面的布局。 rem布局的本质是等比缩放,一般是基于宽度。 兼容性 iOS Android rem 4.1+ 2.1+ See the Pen rem by YeaseonZhang (@YeaseonZhang) on CodePen. 现在移动端设计稿的尺寸大多为以iPhone6为基准的750px,首先将页面划分为100份,每一份的宽度为npx, 设置1rem = 10n,所以以750px为基准的话,每份就是7.5px,1rem就对应75px。但这仅仅只能适配宽度为750px的设备,所以通过加载页面时动态计算设备的尺寸来,修改html的font-size值,就能实现页面缩放适配。 最简单设置rem基准值的方法。1234document.addEventListener('DOMContentLoaded', function (e) { var rem = window.innerWidth / 10 + 'px'; document.getElementsByTagName('html')[0].style.fontSize = rem;}) 使用scss将px转换为rem值1234@function px2rem ($px) { $rem: 75px; @return ($px / $rem) + rem;} 也有人建议将设计稿宽度划分成100份,每一份就是一个rem单位,那么750px宽度的设计稿,对应的html的font-size = 1rem = 7.5px,方便兼容vh/vw单位。但是不建议这么做,你知道什么吗? vh/vwvh/vw单位类似于百分比单位不同之处在于vh/vw单位的布局不依赖于父级的宽高,而是相对于视口的宽高。1vh等于1%的视口高度,1vw等于1%的视口宽度。若视口宽度是750px,那么1vw就是7.5px。 目前移动端高端机型对于视口单位基本全面支持。 兼容性 iOS Android vw 6.1+ 4.4+ See the Pen v-unit by YeaseonZhang (@YeaseonZhang) on CodePen. 注:不同浏览器在获取视口的方法不同IE9+、Firefox、Safari、Opera和Chrome均提供了4个属性innerWidth、innerHeight、outerWidth和outerHeight。 IE9+、Safari和Firefox中,outerWidth和outerHeight返回浏览器窗口本身的尺寸,而innerWidth和innerHeight则表示该容器中页面视图区的大小(减去边框宽度) Chrome中,inner*和outer*返回相同的值,即视口大小而非浏览器窗口的大小。 在IE、Firefox、Safari、Opera和Chrome中,都能通过document.documentElement.clientWidth和document.documentElement.clientHeight中保存了页面视口信息。 获取页面视口大小123456789101112var pageWidth = window.innerWidth, pageHeight = window.innerHeight;if (typeof pageWidth != 'number') { if (document.compatMode == 'CSS1Compat') { // 浏览器标准模式 pageWidth = document.documentElement.clientWidth; pageHeight = document.documentElement.clientHeight; } else { // IE6 混杂模式 pageWidth = document.body.clientWidth; pageHeight = document.doby.clientHeight; }} 其实,vw还可以和rem方案结合,这样就不需要js计算来设置html字体大小。123456html { font-size: 1vw}p { width: 15rem;} 往往一份设计稿为了兼容大屏设备,我们会采取限制布局的最大宽度。大于这个宽度的话页面居中并且,两边会留白。这个时候vw单位就无法满足我们的需求了。 vmin/vmaxvmin是指vh和vw中较小的那一个的大小,当然vmax就是vh和vw中较大的那一个。 例如,浏览器视口宽1100px、高700px,那么1vmin = 7px; 1vmax = 11px;如果浏览器视口宽800px,高1080px,那么1vmin = 8px; 1vmax = 10.8px 很有意思的是,使用这个单位的时候我们并不关心宽高,而是按照大小来区分,所以在移动端中的应用会比较多。 1234.box { height: 100vmin; width: 100vmin;} 1234.box { height: 100vmax; width: 100vmax;} ch 和 exch和ex单位都是基于当前字体的特定单位。 ch单位,被定义为当前字体0字符的宽度。所以如果你使用的是等宽字体,那么你就可以直接定义一个盒子能够容纳多少个字符。 See the Pen ch by YeaseonZhang (@YeaseonZhang) on CodePen. ex单位,被定义为当前字体x字符高度。这个单位通常用于排版微调,确保精确控制。 See the Pen ex by YeaseonZhang (@YeaseonZhang) on CodePen. 小结 em单位,还是老老实实作为字体/行高单位,如果用做布局使用,会牵一发而动全身,一个节点变化导致后代元素都需要重新计算 rem单位,需要结合js脚本动态设置html字体大小,如果用户禁用了js(少数情况),这种时候我们可以选择<noscript>开启JavaScript,获得更高的用户体验</noscript>;第二个方案就是使用媒体查询,为主流尺寸的设备设置html字体大小 vh/vw/vmin/vmax单位,对于设备系统浏览器要求比较高,如果不考虑兼容低版本浏览器,可以选择使用。当你选择使用vw/vh单位的时候,配合calc计算属性会更精确完成布局,例如width: calc(50vw - 40px) ch/ex单位,使用的频率不高。如果使用的是等宽字体,ch单位可以用来布局,但是中文字体和英文字体等宽肯定是不一样宽的,所以还是不要考虑用来布局了。ex单位的应用场景,基本上使用em单位都能完成,所以也是一个可用可不用的单位。 参考资料 7 CSS Units You Might Not Know About 網頁樣式表 CSS提示以及技巧 Rem布局的原理解析]]></content>
<tags>
<tag>css</tag>
</tags>
</entry>
<entry>
<title><![CDATA[前端跨域常用方法]]></title>
<url>%2F2017%2F10%2F12%2F%E5%89%8D%E7%AB%AF%E8%B7%A8%E5%9F%9F%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95%2F</url>
<content type="text"><![CDATA[在开发过程中经常会涉及跨域问题,解决跨域问题的方案也有很多种,接下来就来梳理一下前端跨域的常用方法。 同源策略何为跨域,跨域是相对于同源而言。协议、域名和端口均相同,则为同源。浏览器通过同源策略限制从一个源加载的文档或脚本与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制,摘抄自MDN。 常见解决方案document.domain这种方案主要用于主域相同,子域不同的跨域情况。例如: https://jdc.jd.com/ 和 https://www.jd.com/。 通过在https://www.jd.com/打开一个https://jdc.jd.com/,此时JDC的域名是jdc.jd.com/,通过控制台执行document.domain = 'jd.com';。强制设置主域,实现同源。 12345var jdc = window.open('https://jdc.jd.com/');// JDC 页面加载完成后执行var divs = jdc.document.getElementsByTagName('div');$(divs).css('border', '1px solid red'); 通常的做法是通过iframe加载一个跨域页面资源。因为window.open这个方法在浏览器中会被当做谈广告禁止掉。 http://domain.com/index.html 1234<iframe id="sub" src="http://sub.domain.com/index.html"></iframe><script> var username = 'yeseonzhang';</script> http://sub.domain.com/index.html 1234<script> document.domain = 'domain.com'; console.log(window.parent.username);</script> location.hash这种跨域方法主要是通过设置/监听url的hash部分,来实现跨域,同时需要借助第三个页面来进行辅助。 上图就是三个页面的包含关系,以及hash的传递过程。 http://domain-a.com/a.html 1234567891011<iframe id="iframe-b" src="http://domain-b.com/b.html"></iframe><script> var bPage = document.getElementById('iframe-b'); /* step 1 */ bPage.src = bPage.src + '#user=yeaseonzhang'; function cb (res) { console.log(res); }</script> http://domain-b.com/b.html 123456789<iframe id="iframe-c" src="http://domain-a.com/c.html"></iframe><script> var cPage = document.getElementById('iframe-c'); window.onhashchange = function () { /* step 2 */ cPage.src = cPage.src + location.hash; }</script> http://domain-a.com/c.html 123456<script> window.onhashchange = function () { /* step 3 */ window.parent.parent.cb('success: ' + location.hash); }</script> 由于a页面和c页面是同域资源,所以c页面可以通过window.parent.parent访问a页面资源。 window.name这个方案类似location.hash,需要通过第三个页面进行辅助。window.name属性是用来获取/设置窗口的名称。需要注意的是,当前窗口的window.name并不会因为页面的重载和跳转而更改,所以可以利用这个特性将跨域的window.name通过重定向到同域页面进行读取。 http://domain-a.com/a.html 123456789101112131415161718192021222324<script> var iframe = document.createElement('iframe'); /* step 1 加载跨域页面 */ iframe.src = 'http://domain-b.com/b.html'; var domain = 'diff'; /* 监听iframe加载 */ iframe.onload = function () { if ('diff' == domain) { /* step 2 重定向到同域页面 */ iframe.contentWindow.location = 'http://www.domain-a.com/c.html'; domain = 'same'; } else if ('same' == domain) { /* 获取同域资源的window.name信息 */ cb(iframe.contentWindow.name); /* 清空数据 */ iframe.contentWindow.name = ''; } } function cb (res) { console.log(JSON.parse(res)); }</script> http://domain-b.com/b.html 1234567<scirpt> /* 写入相关数据 */ var obj = { username: 'yeaseonzhang' } window.name = JSON.stringify(obj);</script> http://domain-a.com/c.html 同域c页面,可以是一个空页面,不需要进行任何操作。 JSONPJSONP(JSON with Padding)是JSON的一种使用方式。这种方式允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据。 众所周知,html页面中所有带有src属性的标签(<img>,<script>和iframe)都拥有跨域能力。所以最简单的实现方式就是动态加载JS。 客户端 12345678function todo(data){ console.log('The author is: '+ data.name);}var script = document.createElement('script');/* callback参数,用来指定回调函数的名字。 */script.src = 'http://www.yeaseonzhang.com/author?callback=todo';document.body.appendChild(script); 服务端 12/* 服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。 */todo({"name": "yeaseonzhang"}); todo()函数会被作为全局函数来执行,只要定义了todo()函数,该函数就会被立即调用。 postMessagewindow.postMessage是HTML5中一个安全的,基于事件的消息API。 1otherWindow.postMessage(message, targetOrigin, [transfer]); postMessage(),方法包含三个参数: message: 消息内容 targetOrigin: 接受消息窗口的源,即”协议 + 域名 + 端口”。也可以设置为通配符*,向所有窗口发送 transfer: 可选参数(布尔值),是一串和message 同时传递的Transferable对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。 发送者和接收者都可以通过message事件,监听对方的消息。message事件的事件对象event包含三个属性: event.source: 发送消息的窗口对象的引用,可以用此在两个窗口建立双向通信。 event.origin: 发送消息的URI event.data: 消息内容 发送者: http://domain-a.com/a.html 12345678910111213141516<script> var newWindow = window.open('http://domain-b.com/b.html'); /* 向b.html发送消息 */ newWindow.postMessage('Hello', 'http://domain-b.com/b.html'); /* 双向通信,接收b.html的回复消息 */ var onmessage = function (event) { var data = event.data; var origin = event.origin; var source = event.source; if (origin == 'http://domain-b.com/b.html') { console.log(data); //Nice to see you! } }; window.addEventListener('message', onmessage, false);</scirpt> 接收者:http://domain-b.com/b.html 12345678910111213<script> var onmessage = function (event) { var data = event.data; var origin = event.origin; var source = event.source; if (origin == 'http://domain-a.com/a.html') { console.log(data); //Hello /* 回复a.html的消息 */ source.postMessage('Nice to see you!', 'http://domain-a.com/a.html'); } }; window.addEventListener('message', onmessage, false);</script> WebSocketWebSocket是一种HTML5的一种新的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案,详细介绍请访问MDN。 1234567891011121314151617181920212223242526272829303132333435/* websocket协议为ws/wss, 类似http/https的区别 */wsUrl = 'wss://127.0.0.1:8090/ws/';/* 发送 */ws = new WebSocket(wsUrl);/* 连接成功建立时调用 */ws.onopen = function (event) { console.log("websocket command onopen"); var msg = { username: 'YeaseonZhang' } /* 通过 send() 方法向服务端发送消息,参数必须为字符串 */ ws.send(JSON.stringify(msg));};/* 服务端向客户端发送消息时调用 */ws.onmessage = function (event) { /* event.data包含了服务端发送过来的消息 */ console.log("websocket command onmessage: " + event.data); if (event.data === 'success') { /* 通过 close() 方法断开websocket连接 */ ws.close(); }};/* 连接被关闭时调用 */ws.onclose = function (event) { console.log("websocket command onclose: " + event.data);};/* 出现错误时调用 */ws.onerror = function (event) { console.log("websocket command onerror: " + event.data);}; WebSocket的优势是除了可以实现跨域,还有就是可以保持长连接,而不需要通过轮询实现实时性。 CORSCORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。 只需要后端同学支持就ok,前端不需要做很多额外工作(除了携带cookie)。 只要服务器返回的相应中包含头部信息Access-Control-Allow-Origin: domain-name,domain-name为允许跨域的域名,也可以设置成*,浏览器就会允许本次跨域请求。 结语以上就是我所了解的跨域的解决方案,希望对你有所帮助。]]></content>
<tags>
<tag>web安全</tag>
</tags>
</entry>
<entry>
<title><![CDATA[2017前端面试题及答案总结]]></title>
<url>%2F2017%2F09%2F17%2F2017%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%8A%E7%AD%94%E6%A1%88%E6%80%BB%E7%BB%93%2F</url>
<content type="text"><![CDATA[“金三银四,金九银十”,用来形容求职最好的几个月。但是随着行业的饱和,初中级前端er就业形势不容乐观。 行业状态不可控,我们能做的当然只是让自己变得更加具有竞争力。 今年自己也用了几个月的时间来准备笔记面试,巩固基础知识。特此将自己在这个过程总结的题目分享出来,希望对于求职和准备求职的同学有所帮助。 CSS列举不同的清除浮动的技巧12345678910111213141516171819202122232425262728/* 1.添加新元素 */<div class="outer"> <div class="div1"></div> <div class="div2"></div> <div class="div3"></div> <div class="clearfix"></div></div>.clearfix { clear: both;}/* 2.为父元素增加样式 */.clearfix { overflow: auto; zoom: 1; // 处理兼容性}/* 3.:after 伪元素方法 (作用于父元素) */.outer { zoom: 1; &:after { display: block; height: 0; clear: both; content: '.'; visibillity: hidden; }} 一像素边框使用sass语法。 12345678910111213141516171819202122232425262728293031323334/* 定义 */@mixin border-1px ($color) { position: relative; &:after { display: block; position: absolute; left: 0; bottom: 0; width: 100%; border-top: 1px solid $color; context: ''; }}@media (-webkit-min-device-pixel-radio: 1.5), (min-device-pixel-radio: 1.5) { border-1px { &:after { -webkit-transform: scaleY(0.7); transform: scaleY(0.7); } }}@media (-webkit-min-device-pixel-radio: 2), (min-device-pixel-radio: 2) { border-1px { &:after { -webkit-transform: scaleY(0.5); transform: scaleY(0.5); } }}/* 使用方式 */@inclue border-1px(rgba(7, 17, 27, .1)); 形成BFC(Block Formatting Context)的几种方式BFC全称”Block Formatting Context”, 中文为“块级格式化上下文”。BFC元素特性表现原则就是,内部子元素再怎么翻江倒海,翻云覆雨都不会影响外部的元素。 float为 left|right overflow为 hidden|auto|scroll display为 table-cell|table-caption|inline-block position为 absolute|fixed 布局 圣杯布局和双飞翼布局 左定宽右自适应宽度,并且等高布局(最小高度200px) 123456789101112131415161718192021222324252627282930/* HTML */<div class="container"> <div class="left">Left silder</div> <div class="content">Main content</div></div>/* CSS */.container { overflow: hidden;}.left { float: left; width: 200px; margin-bottom: -9999px; padding-bottom: 9999px; background-color: #eee;}.content { margin-left: 200px; margin-bottom: -9999px; padding-bottom: 9999px; background-color: #ccc;}.left, .content { min-height: 200px; height: auto !important;} JSasync与defer区别异步(async) 脚本将在其加载完成后立即执行,而 延迟(defer) 脚本将等待 HTML 解析完成后,并按加载顺序执行。 location.replace()与location.assign()区别location.replace()的url不会出现在history中 new操作符 创建一个空对象,并且this变量引用该对象,同时还继承了 该函数的原型 属性和方法被加入到this引用的对象中 新创建的对象由this所引用,并且最后隐式的返回this AMD CMD CommonJS12345678910111213141516171819/* AMD是RequireJS对模块化的定义 * CMD是seaJS对模块化的定义 * CommonJS是Node对模块化的规范 **//* AMD 依赖关系前置 */define(['./a', './b'], function (a, b) { a.something(); b.something();})/* CMD 按需加载,依赖就近 */define(function (require, exports, module) { var a = require('./a'); a.something(); var b = require('./b'); b.something();}) DOM 操作123456789101112131415161718// 创建节点createDocumentFragment()createElement()createTextNode()// 添加 移除 替换 插入appendChild()removeChild()replaceChild()insertBefore()// 查找getElementsByTagName()getElementsByName()getElementsByClassName()getElementById()querySelector()querySelectorAll() JS设置css样式的几种方式1234567891011121314/* 1.直接设置style属性 */element.style.height = '100px';/* 2.直接设置属性 */element.setAttribute('height', '100px');/* 3.使用setAttribute设置style属性 */element.setAttribute('style', 'height: 100px !important');/* 4.使用setProperty设置属性,通过第三个参数设置important */element.style.setProperty('height', '300px', 'important');/* 5.设置cssText */element.style.cssText += 'height: 100px !important'; 阻止默认行为12345678910function stopDefault( e ) { // 阻止默认浏览器动作(W3C) if ( e && e.preventDefault ) { e.preventDefault(); } else { // IE中阻止函数器默认动作的方式 window.event.returnValue = false; } return false;} 阻止冒泡12345678910function stopBubble(e) { // 如果提供了事件对象,则这是一个非IE浏览器 if ( e && e.stopPropagation ) { // 因此它支持W3C的stopPropagation()方法 e.stopPropagation(); } else { // 否则,我们需要使用IE的方式来取消事件冒泡 window.event.cancelBubble = true; }} Ajax交互过程 创建XMLHttpRequest对象,也就是创建一个异步调用对象. 创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息. 设置响应HTTP请求状态变化的函数. 发送HTTP请求. 获取异步调用返回的数据. 使用JavaScript和DOM实现局部刷新. 考察知识点最广的JS面试题1234567891011121314151617function Foo() { getName = function () { alert(1); } return this;}Foo.getName = function () { alert(2); }Foo.prototype.getName = function () { alert(3); }var getName = function () { alert(4); }function getName () { alert(5); }/* 写出输出 */Foo.getName();getName();Foo().getName();getName();new Foo.getName();new Foo().getName();new new Foo().getName(); 具体讲解参见一道常被人轻视的前端JS面试题 JS数组深浅拷贝 slice实现 12345678var arr = ['old', 1, true, null, undefined];var new_arr = arr.slice();new_arr[0] = 'new';console.log(arr) // ["old", 1, true, null, undefined]console.log(new_arr) // ["new", 1, true, null, undefined] concat实现 12345678var arr = ['old', 1, true, null, undefined];var new_arr = arr.concat();new_arr[0] = 'new';console.log(arr) // ["old", 1, true, null, undefined]console.log(new_arr) // ["new", 1, true, null, undefined] 以上两种方法只是浅拷贝,如果数组元素是基本类型,就会拷贝一份新的;但是如果数组元素是对象或者数组,就只会拷贝引用(类似指针),修改其中一个就会影响另外一个。 123456789var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}];var new_arr = arr.concat();new_arr[0] = 'new';new_arr[3][0] = 'new1';console.log(arr) // ["old", 1, true, ['new1', 'old2'], {old: 1}]console.log(new_arr) // ["new", 1, true, ['new1', 'old2'], {old: 1}] JSON.stringify实现数组深拷贝 123456789var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}];var new_arr = JSON.parse(JSON.stringify(arr));new_arr[0] = 'new';new_arr[3][0] = 'new1';console.log(arr) // ["old", 1, true, ['old1', 'old2'], {old: 1}]console.log(new_arr) // ["new", 1, true, ['new1', 'old2'], {old: 1}] 简单粗暴,但是问题是不能拷贝函数,不推荐。 然后我们来手动实现深浅拷贝。 浅拷贝 12345678910111213var shallowCopy = function (obj) { // 判断是否是数组或者对象 if (typeof obj !== 'object') { return } var newObj = obj instanceof Array ? [] : {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } } return newObj;} 深拷贝 123456789101112var deepCopy = function (obj) { if (typeof obj !== 'object') { return } var newObj = obj instanceof Array ? [] : {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]; } } return newObj} 数组去重 filter + indexOf 123456function unique (arr) { var res = arr.filter(function (item, index, array) { return array.indexOf(item) === index; }) return res;} filter + sort 12345function unique (arr) { return arr.concat().sort().filter(function (item, index, array) { return !index || item !== array[index - 1]; })} ES6 123function uniqu3 (arr) { return [... new Set(arr)];} 找出数组中的最大值 reduce 1234567var arr = [6, 4, 1, 8, 2, 11, 3];function max (prev, next) { return Math.max(prev, next)}console.log(arr.reduce(max)); apply 123var arr = [6, 4, 1, 8, 2, 11, 3];console.log(Math.max.apply(null, arr)); ES6 1234567var arr = [6, 4, 1, 8, 2, 11, 3];function max (arr) { return Math.max(...arr);}console.log(max(arr)); 打乱数组的方法12345678var arr = [];for (var i = 0; i < 100; i++) { arr[i] = i;}arr.sort(function () { return 0.5 - Math.random();}); 数组扁平化123456789101112var arr = [1, [2, [3, 4]]];function flatten(arr) { while (arr.some(item => Array.isArray(item))) { arr = [].concat(...arr); } return arr;}console.log(flatten(arr)) 排序12345678910111213141516171819202122232425262728293031323334353637// 冒泡function bubbleSort2(arr) { var len = arr.length; for (var i = 0; i <= len - 1; i++) { for (var j = 0; j <= len - i; j++) { if (arr[j + 1] < arr[j]) { var temp; temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } return arr;}// 快速排序function quickSort(arr) { if(arr.length == 0) { return []; // 返回空数组 } var cIndex = Math.floor(arr.length / 2); var c = arr.splice(cIndex, 1); var l = []; var r = []; for (var i = 0; i < arr.length; i++) { if(arr[i] < c) { l.push(arr[i]); } else { r.push(arr[i]); } } return quickSort(l).concat(c, quickSort(r));} 数字格式化 1234567890 -> 1,234,567,890123456function formatNum (num) { return num.replace(/\B(?=(\d{3})+(?!\d))/g, ',');}var num = '1234567890';var res = formatNum(num);console.log(res); 打乱数组的方法12345678var arr = [];for (var i = 0; i < 100; i++) { arr[i] = i;}arr.sort(function () { return 0.5 - Math.random();}) 尾调用优化即只保存内层函数的调用帧(只有开启严格模式,才会生效),只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则无法进行“尾调用优化。123456789101112function factorial(n) { if (n === 1) return 1; return n * factorial(n-1);}factorial(5)/* 优化尾递归 */function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total);}factorial(5, 1) 柯里化实现add(1,2)和add(1)(2)均输出3123456789101112131415161718192021222324252627function add () { let sum = 0; Array.prototype.forEach.call(arguments, function (item, index){ if (typeof item !== 'number') { return false; } else { sum += item; } }) var tmp = function () { Array.prototype.forEach.call(arguments, function (item, index){ if (typeof item !== 'number') { return false; } else { sum += item; } }) return tmp; } tmp.toString = function () { return sum } return tmp;}add(1, 2); // 3add(1)(2); // 3add(1, 2, 3)(1, 4)(2, 2)(1) // 16 ES8 新特性 字符串填充 12str.padStart(targetLength [, padString])str.padEnd(targetLength [, padString]) values和entries函数 12Object.values(obj)Object.entries(obj) getOwnPropertyDescriptors函数 1Object.getOwnPropertyDescriptors(obj) 函数参数逗号结尾 1function es8(var1, var2, var3,) {} 异步函数由async关键词定义的函数声明了一个可以异步执行的函数,返回一个AsyncFunction类型的对象。 123456789101112fucntion fetchTextByPromise () { return new Promise(resolve => { setTimeout(() => { resolve('es8'); }, 2000); });}async function sayHello () { const externalFetchedText = await fetchTextByPromise(); console.log(`Hello, ${externalFetchedText}`);}sayHello(); 数据类型判断12345678910var class2type = {};'Boolean Number String Function Array Date RegExp Object Error Null Undefined'.split(' ').map((item, index) => { class2type['[object ' + item + ']'] = item.toLowerCase();})function type (obj) { return typeof obj === 'object' || typeof obj === 'function' ? class2type[{}.toString.call(obj)] || 'object' : typeof obj;} 防抖123456789101112131415161718192021222324252627282930/* * func:需要调用的函数 * wait: 防抖时间 * immediate:布尔值,是否立即执行 **/var debounce = function (func, wait, immediate) { var timeout; return function () { var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { // 是否立即执行func var callNow = !timeout; timeout = setTimeout(function () { timeout = null; }, wait); if (callNow) { func.apply(context, args); } } else { timeout = setTimeout(function () { func.apply(context, args); }, wait); } }} 简单的字符串模板123456789101112131415var TemplateEngine = function(tpl, data) { var re = /<%([^%>]+)?%>/g, match; while(match = re.exec(tpl)) { tpl = tpl.replace(match[0], data[match[1]]) } return tpl;}var template = '<p>Hello, my name is <%name%>. I\'m <%age%> years old.</p>';console.log(TemplateEngine(template, { name: "Yeaseon", age: 24}));// '<p>Hello, my name is Yeaseon. I\'m 24 years old.</p>'; apply、call和bind apply和call 在严格模式下,未指定环境对象而调用函数,则this 值不会转型为window。除非明确把函数添加到某个对象或者调用apply()或call(),否则this 值将是undefined。 在非严格模式下,call、apply的第一个参数传递为null或undefined时,函数体内的this会指向默认的宿主对象,在浏览器中则是window。 apply、call和bind比较 12345678910111213var obj = { x: 81};var foo = { getX: function () { return this.x; }}console.log(foo.getX.bind(obj)());console.log(foo.getX.apply(obj));console.log(foo.getX.call(obj)); 很明显,bind方法后面多了一对括号。也就是说,当你希望改变上下文环境之后并且立即执行,而是回调执行的时候(多用于事件监听器函数),使用bind()方法,而apply/call则会立即执行函数。 定义一个 log 方法,让它可以代理 console.log 方法。 12345function log(){ console.log.apply(console, arguments);};log(1); //1log(1,2); //1 2 给每一个 log 消息添加一个”(app)”的前辍,比如: 1log("hello world"); //(app)hello world 12345function log(){ var args = Array.prototype.slice.call(arguments); args.unshift('(app)'); console.log.apply(console, args);}; apply实现bind 12345function bind (fn, context) { return function () { return fn.apply(context, argument); }} 创建对象 工厂模式 123456789101112function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o;}var person1 = createPerson("Nicholas", 29, "Software Engineer");var person2 = createPerson("Greg", 27, "Doctor"); 构造函数模式 12345678910function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); };}var person1 = new Person("Nicholas", 29, "Software Engineer");var person2 = new Person("Greg", 27, "Doctor"); 原型模式 12345678910function Person(){}Person.prototype.name = "Nicholas";Person.prototype.age = 29;Person.prototype.job = "Software Engineer";Person.prototype.sayName = function(){ alert(this.name);};var person1 = new Person();person1.sayName(); //"Nicholas" 构造函数 + 原型模式 12345678910111213141516171819function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"];}Person.prototype = { constructor : Person, sayName : function(){ alert(this.name); }}var person1 = new Person("Nicholas", 29, "Software Engineer");var person2 = new Person("Greg", 27, "Doctor");person1.friends.push("Van");alert(person1.friends); //"Shelby,Count,Van"alert(person2.friends); //"Shelby,Count"alert(person1.friends === person2.friends); //falsealert(person1.sayName === person2.sayName); //true JS实现Jquery的extend()方法123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960function extend() { // 默认不进行深拷贝 var deep = false; var name, options, src, copy, clone, copyIsArray; var length = arguments.length; // 记录要复制的对象的下标 var i = 1; // 第一个参数不传布尔值的情况下,target 默认是第一个参数 var target = arguments[0] || {}; // 如果第一个参数是布尔值,第二个参数是 target if (typeof target == 'boolean') { deep = target; target = arguments[i] || {}; i++; } // 如果target不是对象,我们是无法进行复制的,所以设为 {} if (typeof target !== "object" && !isFunction(target)) { target = {}; } // 循环遍历要复制的对象们 for (; i < length; i++) { // 获取当前对象 options = arguments[i]; // 要求不能为空 避免 extend(a,,b) 这种情况 if (options != null) { for (name in options) { // 目标属性值 src = target[name]; // 要复制的对象的属性值 copy = options[name]; // 解决循环引用 if (target === copy) { continue; } // 要递归的对象必须是 plainObject 或者数组 if (deep && copy && (isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) { // 要复制的对象属性值类型需要与目标属性值相同 if (copyIsArray) { copyIsArray = false; clone = src && Array.isArray(src) ? src : []; } else { clone = src && isPlainObject(src) ? src : {}; } target[name] = extend(deep, clone, copy); } else if (copy !== undefined) { target[name] = copy; } } } } return target;}; 自定义事件(通过观察者模式)1234567891011121314151617181920212223242526272829303132333435function EventTarget () { this.handlers = {};}EventTarget.prototype = { constructor: EventTarget, addHandler: function (type, handler) { if (typeof this.handlers[type] == 'undefined') { this.handlers[type] = []; } this.handlers[type].push(handler) }, fire: function (event) { if (!event.target) { event.target = this; } if (this.handlers[event.type] instanceof Array) { var handlers = this.handlers[event.type]; for (var i = 0, len = handlers.length; i < len; i++) { handlers[i](event); } } }, removeHandler: function (type, handler) { if (this.handlers[type] instanceof Array) { var handlers = this.handlers[type]; for (var i = 0, len = handlers.length; i < len; i++) { if (handlers[i] === handler) { break; } } handlers.splice(i, 1); } }} 安全跨域的几种方法 主域相同的跨域 1document.domain window.postMessage JSONP跨域(只支持GET) 1234567891011function todo(data){ console.log('The author is: '+ data.name);}var script = document.createElement('script');/* 向服务器www.yeaseonzhang.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字。 */script.src = 'http://www.yeaseonzhang.com/author?callback=todo';document.body.appendChild(script);/* 服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。 */todo({"name": "fewjq"});/* 由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了todo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象。*/ websocket跨域 XSS 和 CSRF XSS CSRF 性能CSS 优化 正确的时机调用CSS 使用媒体查询标记<link>,选择性加载 减少css文件数量 压缩css代码 渲染 浏览器渲染原理]]></content>
<tags>
<tag>面试</tag>
</tags>
</entry>
<entry>
<title><![CDATA[深入理解Flex属性]]></title>
<url>%2F2017%2F07%2F05%2F%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Flex%E5%B1%9E%E6%80%A7%2F</url>
<content type="text"><![CDATA[Flex 是 Flexible Box的缩写,意为“弹性布局”,可以简便、完整、响应式地实现各种页面布局。 Flex的出现就是为了解决传统布局的display属性 + position属性 + float属性的复杂性。 浏览器对于Flex的支持性 可以看到浏览器对于Flex布局的支持性已经很好了,所以我们可以放心大胆的用在我们项目中。 flex 属性flex: flex-grow | flex-shrink | flex-basis flex属性包含三个值:flex-grow、flex-shrink和flex-basis flex-grow: 扩展比例 flex-shrink: 收缩比例 flex-basis: 伸缩基准值 首先我们简单写一个flex布局的Demo HTML 12345<div class="parent"> <div class="child_1"></div> <div class="child_2"></div> <div class="child_3"></div></div> CSS 123456789101112131415161718.child_1,.child_2,.child_3 { flex: 1; height: 200px;}.child_1 { background-color: rgba(255, 0, 0, .5);}.child_2 { background-color: rgba(0, 255, 0, .5);}.child_3 { background-color: rgba(0, 0, 255, .5);} See the Pen Flex-1 by YeaseonZhang (@YeaseonZhang) on CodePen. 设置父元素display: flex,其子元素就能使用flex布局,我们只是简单的为子元素使用了flex: 1就实现了三等分,而且会随着父元素的宽度变化而变化。这就是我们平时flex布局的简单用法,即各个子元素按照比例布局。 flex: 1其实是flex: 1 1 0%的简写属性,即伸缩比例都是1。下面我们就分别理解下三个属性值对于布局的影响。 flex-basis 属性flex-basis属性是伸缩的基准值,这个属性也是我们计算最终宽度的决定性因素。 通过子元素flex-basis属性和与父元素(容器)宽度值进行比较,会出现两种情况(忽略相等的情况): 子元素flex-basis属性和 < 父元素宽度 子元素flex-basis属性和 > 父元素宽度 上面的两种情况就分别对应了flex-grow和flex-shrink属性生效的情况,也就是说当子元素的flex-basis属性宽度和小于父元素的宽度值时flex-grow生效,反之flex-shrink生效。 flex-grow 属性flex-grow属性是扩展比例,上面我们也谈到了当子元素的flex-basis总和小于父元素的宽度值时flex-grow生效。 现在我们就来改变CSS,满足这个前提条件。 CSS 12345678910111213141516171819202122232425.parent { display: flex; width: 600px;}.child_1,.child_2,.child_3 { height: 200px;}.child_1 { flex: 1 0 150px; background-color: rgba(255, 0, 0, .5);}.child_2 { flex: 0 0 100px; background-color: rgba(0, 255, 0, .5);}.child_3 { flex: 1 0 150px; background-color: rgba(0, 0, 255, .5);} See the Pen Flex-2 by YeaseonZhang (@YeaseonZhang) on CodePen. 此时,各个元素的flex-basis和为(150 + 100 + 150) = 400px, 小于父元素的600px,我们就来分别计算每个子元素的宽度值。123可用空间 = 父元素width - 子元素flex-basis总和 => 600 - (150 + 100 + 150) = 200单位扩展空间 = 可用空间/子元素flex-grow总和 => 200/(1 + 0 + 1) = 100 子元素的计算公式为width = flex-basis + flex-grow * 单位扩展空间 所以child_1宽度为(150 + 1 * 100) = 250px, child_2宽度为(100 + 0 * 100) = 100px, child_3 同 child_1。 flex-shrink 属性flex-shrink 属性是收缩比例,当子元素的flex-basis总和大于父元素的宽度值时flex-grow生效。 现在我们就修改CSS满足这个前提。 CSS 1234567891011121314151617181920212223242526.parent { display: flex; width: 600px;}.child_1,.child_2,.child_3 { /*flex: 1;*/ height: 200px;}.child_1 { flex: 0 1 400px; background-color: rgba(255, 0, 0, .5);}.child_2 { flex: 0 1 200px; background-color: rgba(0, 255, 0, .5);}.child_3 { flex: 0 2 400px; background-color: rgba(0, 0, 255, .5);} See the Pen Flex-3 by YeaseonZhang (@YeaseonZhang) on CodePen. 当然啦,我们可以按照flex-grow的计算方法套用。 123溢出空间 = 父元素width - 子元素flex-basis总和 => 600 - (400 + 200 + 400) = -400单位收缩空间 = 溢出空间/子元素flex-shrink总和 => -400/(1 + 2 + 2) = -100 子元素的计算公式为width = flex-basis + flex-shrink * 单位收缩空间 所以child_1宽度为(400 + 1 * (-100)) = 300px, child_2宽度为(200 + 1 * (-100)) = 100px, child_3 同 (400 + 2 * (-100)) = 200px。 大功告成了?其实并没有实例情况并不是我们计算的那样300px 100px 200px,而是285.72px 142.86px 171.42px。 通过Google,发现了一种收缩因数的计算方法:12345理想空间 = 子元素(flex-basis * flex-shrink)之和 => 400 * 1 + 200 * 1 + 400 * 2 = 1400溢出空间 = 父元素width - 子元素flex-basis总和 => 600 - (400 + 200 + 400) = -400收缩因数 = (flex-basis * flex-shrink) / 理想空间 => 400 / 1400 = 0.286; 200 / 1400 = 0.143; 800 / 1400 = 0.571 分别为每个子元素计算了收缩因数就能计算我们子元素的实际宽度,子元素的计算公式width = flex-basis + 收缩因数 * 溢出空间 所以child_1宽度为400 + 0.286 * (-400) = 285.6, child_2宽度为200 + 0.143 * (-400) = 142.8, child_3宽度为400 + 0.571 * (-400) = 171.6 现在我们计算出的值与浏览器渲染出的值基本上是相同的,我们对于flex属性的了解已经不再是单单的比例计算了,希望本文对你有所帮助。]]></content>
</entry>
<entry>
<title><![CDATA[CSS布局]]></title>
<url>%2F2017%2F07%2F05%2FCSS%E5%B8%83%E5%B1%80%2F</url>
<content type="text"><![CDATA[由于最近工作比较饱和,也没有太多的心思去写博客。今天更新一篇,我们来谈谈圣杯布局和双飞翼布局。 圣杯布局 HTML 1234567891011121314151617<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Holy Grail Layout</title> <link rel="stylesheet" href="./base.css"></head><body> <div class="container"> <div class="main"></div> <div class="sub"></div> <div class="extra"></div> </div></body></html> CSS 1234567891011121314151617181920212223242526272829303132333435body { min-width: 600px;}.container { padding-left: 210px; padding-right: 190px;}.main { float: left; width: 100%; height: 300px; background-color: rgba(255, 0, 0, .5);}.sub { position: relative; left: -210px; float: left; width: 200px; height: 300px; margin-left: -100%; background-color: rgba(0, 255, 0, .5);}.extra { position: relative; right: -190px; float: left; width: 180px; height: 300px; margin-left: -180px; background-color: rgba(0, 0, 255, 0.5);} 双飞翼布局 HTML 1234567891011121314151617181920<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Double Wing Layout</title> <link rel="stylesheet" href="./base.css"></head><body> <div class="main-wrapper"> <div class="main"></div> </div> <div class="sub"></div> <div class="extra"></div></body></html> CSS 123456789101112131415161718192021222324252627.main-wrapper { float: left; width: 100%;}.main { height: 300px; margin-left: 210px; margin-right: 190px; background-color: rgba(255, 0, 0, .5);}.sub { float: left; width: 200px; height: 300px; margin-left: -100%; background-color: rgba(0, 255, 0, .5);}.extra { float: left; width: 180px; height: 300px; margin-left: -180px; background-color: rgba(0, 0, 255, .5);}]]></content>
</entry>
<entry>
<title><![CDATA[CSS设计指南·读书笔记]]></title>
<url>%2F2017%2F05%2F14%2FCSS%E8%AE%BE%E8%AE%A1%E6%8C%87%E5%8D%97%C2%B7%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%2F</url>
<content type="text"><![CDATA[前端开发中,JS很重要,但CSS也不能轻视,我们来一场CSS学习之旅。 CSS 工作原理特殊的上下文选择符子选择符 >标签1 > 标签2 标签2必须是标签1的子元素,反过来说,标签1是标签2的父元素。 紧邻同胞选择符 +标签1 + 标签2 标签2必须紧跟在其同胞标签1的后面。 一般同胞选择符 ~标签1 ~ 标签2 标签2必须跟在其同胞标签1后面。 通用选择符 *通用选择符*是一个通配符,它匹配任何元素。不过,一般在使用*选择符时,都会同时使用另一个选择符,比如:123p * { color: red;} 这样只会把p包含的所有元素的文本变成红色。 还可以用它构成非子选择符,比如:123section * a { font-size: 14px;} 所有section孙子元素a都会被选中,至于a的父元素是什么,没有关系。 ID和类选择符ID 属性用于页内导航ID123<a href="#first">First title</a><h2 id="first">First title</h2> href属性以#开头,#first就是一个页面的锚点,会与使用了CSS选择符里面相同的id="first"元素做匹配,就会把页面定位到h2#first的位置,这也就是为什么CSS的id选择器在页面中只能使用一次。 属性选择符属性名选择符标签名[属性名] 选择任何带有属性名的标签名 属性值选择符标签名[属性名='属性值'] 注:在HTML5中,属性值的引号可以不加,但是推荐添加。 伪类伪类这个叫法源自它们与类相似,但实际上没有类会附加到标记中的标签上。 UI伪类会在HTML元素处于某个状态时,为该元素应用CSS样式。 结构化伪类会在标记中存在某个结构上的关系时,为相应元素应用CSS样式。 UI伪类链接伪类 Link,未点击状态 Visited,点击过这个链接 Hover,鼠标悬停在这个链接上 Active,链接正在被点击(鼠标在元素上按下,还没有释放) 1234a:link { color: black; }a:visited { color: gray; }a:hover { text-decoration: none; }a:active { color: red; } 按照LoVe HA的顺序使用它们,否则浏览器可能不会显示预期结果。 一个冒号(:)表示伪类,两个冒号(::)表示CSS3新增伪类。 :focus 伪类e:focus 在后续例子中,e表示任何元素,不再以文字表示 获得焦点时,:focus伪类生效1input:focus { border: 1px solid blue; } :target 伪类e:target 如果用户点击一个指向页面中其他元素的链接,则那个元素就是target,可以用:target伪类选中它。 123456<a href="#more_info">More Information</a><h2 id="more_info">This is the information you are looking for.</h2>// CSS#more_info:target { background: #eee } 当用户点击链接转向ID为more_info的元素时,为该元素添加#eee背景。 结构化伪类:first-child 和 :last-childe:first-childe:last-child :first-child代表一组同胞元素中的第一个元素,而:last-child代表最后一个。1234567891011// cssol.results li:first-child { color:blue;}// html<ol class="results"> <li>My Fast Pony</li> <li>Steady Trotter</li> <li>Slow Ol' Nag</li></ol> 第一个<li>就会被选中,My Fast Pony就会变成蓝色。 :nth-childe:nth-child(n) n 表示一个数值(也可以是 odd 或 even) 1li:nth-child(3) {...} 伪元素伪元素就是文档中若有实无的元素。 ::first-letter 伪元素e::first-letter 123p::first-letter { font-size: 300%;} 上面的CSS样式就可以得到段落首字母放大的效果。 ::first-line 伪元素e::first-line 可以选中文本段落的第一行。 ::before 和 ::after 伪元素e::beforee::after 可用于在特定元素前面或后面添加特殊内容。123456789<p class="age">25</p>// cssp.age::before { content: 'Age: ';}p.age::after { content: ' years.';} 就能得到结果:Age: 25 years. 层叠计算特指度下面具体讲讲怎么计算选择符的特指度。首先,有一个简单的计分规则,即每个选择符都要按下面的“ICE”公式计算三个值:I - C - E (ID Class Element) 针对这个公式的计分方法如下: 选择符中有一个ID,就在I的位置上加1 选择符中有一个类,就在C的位置上加1 选择符中有一个元素名,就在E的位置上加1 得到一个三位数 123456p 0-0-1 特指度=1p.largetext 0-1-1 特指度=11p#largetext 1-0-1 特指度=101body p#largetext 1-0-2 特指度=102body p#largetext ul.mylist 1-1-3 特指度=113body p#largetext ul.mylist li 1-1-4 特指度=114 规则声明数字值em 和 ex 都是字体大小的单位,但在CSS中,它们作为长度单位适用于任何元素。 em,它表示一种字体中字母M的宽度,因此大小取决于字体 ex,表示给定字体中字母x的高度,不包括字母上、下突出的部分 颜色值颜色名 aqua 浅绿色 black 黑色 blue 蓝色 fuchsia 紫红色 gray 灰色 green 绿色 lime 黄绿色 maroon 褐红色 navy 深蓝色 olive 茶青色 purple 紫色 red 红色 sliver 银色 teal 青色 white 白色 yellow 黄色 以上就是W3C定义了16个颜色的关键字。 定位元素盒子有多大盒模型结论一:没有(就是没有设置width的)宽度的元素始终会扩展到填满其父元素的宽度为止。添加水平边框、内边距和外边距,会导致内容宽度减少,减少量等于水平边框、内边距和外边距的和。 盒模型结论二:为设定了宽度的盒子添加边框、内边距和外边距,会导致盒子扩展得更宽。实际上,盒子的width属性设定的只是盒子内容区的宽度,而非盒子要占据的水平宽度。 CSS3新增的box-sizing属性,强制转换盒子模型。 浮动与清除围住浮动元素的三种方法 为父元素添加overflow:hidden 同时浮动父元素float: left/right 添加非浮动的清除元素或者给父元素添加clearfix类 123456789101112131415// HTML<section class="clearfix"> <img src="images/rubber_duck.jpg"> <p>It's fun to float.</p></section><footer>Here is the footer element...</footer>// CSS.clearfix:after { content: '.'; display: block; height: 0; visibility:hidden; clear:both;} 背景背景属性CSS规定的与背景相关的属性: background-color background-image background-repeat background-position background-size background-attachment background background-clip background-origin background-break 背景渐变渐变点渐变点就是渐变方向上的点,可以在这些点上设定颜色和不透明度。123456.gradient1 { background:linear-gradient(#64d1dd, #fff 50%, #64d1dd);}.gradient2 { background:linear-gradient(#e86a43 20%, #fff 50%, #e86a43 80%);} 第一个例子在50%处包含一个渐变点。注意,开始和结束位置如果没有声明,则默认为0% 和 100%。 如果不是使用百分比或其他值声明渐变点的位置,则三种颜色会均匀分布于整个渐变,其实际位置是0%、50%和100%。 放射性渐变123456789.gradient1 { background: -webkit-radial-gradient(#fff, #64d1dd, #70aa25);}.gradient2 { background: -webkit-radial-gradient(circle, #fff, #64d1dd, #e86a43);}.gradient3 { background: -webkit-radial-gradient(50px 30px, circle, #fff, #64d1dd, #4947ba);} 三个三色放射性渐变。第一个是默认的填满图形渐变,第二个是圆形渐变,第三个是指定位置的圆形渐变 字体和文本字体网页中的字体的三个来源: 用户机器中安装的字体 保存在第三方网站上的字体 保存在你的Web服务器上的字体,通过@font-face引入。 与字体样式相关的6个属性: font-family font-size font-style font-weight font-variant font 字体族 font-family通用的字体类: serif,也就是衬线字体,在每个字符笔画的末尾会有一些装饰线 sans-serif,无衬线字体,字符笔画的末尾没有装饰线 monospace,等宽字体 cursive,草书体或者手写体 fantasy,不能归入其他类的字体 字体大小 font-size浏览器样式表在设定所有元素的字体大小时,使用的都是相对单位em。默认情况下,1em等于16像素,这也是font-size的基准大小。 绝对字体大小设定绝对字体大小时,也是可以使用关键字值,比如x-small、medium、x-large等等。 相对字体大小使用百分比、em或rem等设置字体大小。如果给某个元素设定了相对字体大小,则该元素的字体大小要相对于最近的”被设定过字体大小”的祖先元素来确定。 如果想使用em,但有需要设定具体的像素大小,可以通过把body的font-size设定为62.5%。这样就把基准大小从16像素改为10像素(16 * 62.5% = 10)。这样em与像素之间的换算关系就更加明显了,1em 等于 10像素。 关于rem单位CSS3新增了一个相对单位rem(root em)。与em 区别在于使用rem 为元素设定字体大小时,仍然是相对大小,但相对于的只是HTML根元素。 字体样式 font-style italic 斜体 oblique 倾斜体 normal 正体 字体变化 font-variant small-caps normal small-caps会导致所有小写英文字母变成小型大写字母(就是大小与小写字母相同的大写字母)。 字体 fontfont属性是一个简写形式,但是使用这个简写需要遵守两条规则,否则浏览器无法正确解释声明的值。规则一:必须声明font-size和font-family的值。规则二:所有值必须按如下顺序声明 font-weight、font-style和font-variant部分先后 然后是font-size 最后是font-family 文本属性CSS的文本属性: line-height letter-spacing text-align text-decoration text-indent text-transform word-spacing vertical-align 文本缩进 text-indenttext-indent属性设定行内盒子相对于包含元素的起点。默认情况下,这个起点就是包含元素的左上角。 注:有个继承问题,需要说明一下这里有一个非常重要的问题,必须请读者注意:text-indent 是可以被子元素继承的。如果你在一个div 上设定了text-indent 属性,那么div 中的所有段落都会继承该缩进值。然而,与所有继承的CSS 值一样,这个缩进并不是祖先元素中设定的值,而是计算的值。下面举一个例子说明。假设有一个400 像素宽的div,包含的文本缩进5%,则缩进的距离是20 像素(400 的5%)。在这个div 中有一个200 像素宽的段落。作为子元素,它继承父元素的text-indent 值,所以它包含的文本也缩进。但继承的缩进是多少呢?不是5%,而是20 像素。也就是说,子元素继承的是根据父元素宽度计算得到的缩进值。结果,虽然段落只有父元素一半宽,但其中的文本也会缩进20 像素。这样可以确保无论段落多宽,它们的缩进距离都一样。当然在子元素上重新设定text-indent 属性,可以覆盖继承的值。 字符间距 letter-spacingletter-spacing为正值时增大字符间距,为负值时缩小间距。 注:无论设定字体大小时使用的是什么单位,设定字符间距一定要用相对单位,以便字间距能随字体大小同比例变化。 单词间距 word-spacing单词间距与字符间距很相似,区别在于只调整单词间距,而不影响字符间距。 文本装饰 text-decoration underline 下划线 overline 上划线 line-through 中划线 blink 闪烁效果 none 文本对齐 text-align left 左对齐 right 右对齐 center 居中 justify 两端对齐 行高 line-heightCSS通过line-height属性实现了印刷行业中常说的加铅条,铅条在活字排版时代用于在文本行之间增加间距。 文本转换 text-transform none uppercase 大写 lowercase 小写 capitalize 单词首字母大写 text-transform属性用于转换元素中文本的大小写。 垂直对齐 vertical-align sub super top middle bottom 任意长度值 vertical-align 以基线为参照上下移动文本,但这个属性只影响行内元素。如果你想在垂直方向上对齐块级元素,必须把其display 属性设定为inline。 页面布局布局的基本概念多栏布局有三种基本的实现方法:固定宽度、流动、弹性。 三栏-中栏流动布局 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283<div id="main_wrapper"> <header> </header> <div id="threecolwrap"> <div id="twocolwrap"> <nav> </nav> <article> </article> </div> <aside> </aside> </div> <footer> </footer></div>// sass* { margin: 0; padding: 0;}body { font: 1em helvetica, arial, sans-serif;}div#main_wrapper { min-width: 600px; max-width: 1100px; margin: 0 auto; header { padding: 5px 10px; background: #3f7ccf; } div#threecolwrap { float: left; width: 100%; div#twocolwrap { float: left; width: 100%; margin-right: -210px; nav { float: left; width: 150px; background: #f00; padding: 20px 0; & > * { margin: 0 10px; } } article { width: auto; margin-left: 150px; margin-right: 210px; background: #eee; padding: 20px 0; & > * { margin: 0 20px; } } } aside { float: left; width: 210px; background: #ffed53; padding: 20px 0; & > * { margin: 0 10px; } } } footer { clear: both; width: 100%; text-align: center; background: #000; }} 界面组件弹出层堆叠上下文和 z-indexz-index值较大的元素,在堆叠层次中位于z-index值较小的元素上方。z-index属性值可以是0到任意大的数值,默认值是auto即为0。 不过z-index只对脱离了普通流的元素才有效,也就是position为absolute、relative或者fixed定位才可以。 CSS3实战页眉 盒阴影 box-shadow box-shadow: 4px 4px 5px 8px #aaa inset; box-shadow属性的这几个值分别代表:水平偏移量、垂直偏移量、模糊量、扩展量、颜色和阴影位置(默认边框外部,即outset)。 水平居中 注:不能给display: inline-block元素设定外边距为auto,所以无法通过margin: 0 auto实现居中。只能通过为父元素添加text-align: center实现居中。 个人理解display: inline-block元素就是行内元素,不能像块状元素那样定位。 垂直居中 如果想要在一个固定高度的元素垂直居中一行文本,可以把这一行文本的line-height设定为该元素的高度。12text-align: center; /* 水平居中 */line-height: xx px; /* 垂直居中: 行高=容器高度 */ 如果垂直居中其他元素,比如图片,可以将容器的display: table-row,再设定(只对单元格有效)vertical-align: middle。123display: table-cell; /* 使用表格的行为 */vertical-align: middle; /* 垂直居中 */text-align: center; /* 水平居中 */ 专题区 文本阴影 text-shadow text-shadow: 4px 4px 5px #aaa; text-shadow 这几值的含义按顺序分别是:水平偏移量、垂直偏移量、模糊量和颜色。与盒阴影不同的是,文本阴影没有扩展量。 图书区CSS3变换transform属性能够调用函数,调用不同的变换函数可以实现不同的形式的变换,而通过传入的参数可以控制变换的结果。 transform: 函数名() scale:用于放大或者缩小元素 rotate:根据指定的度数旋转元素 skew:让元素在X轴和Y轴方向倾斜 translate:根据指定的距离沿X轴和Y轴平移对象 响应式设计媒体查询媒体类型 all:匹配所有设备 handled:匹配手持设备 print:匹配分页媒体或打印预览模式下的屏幕 screen:匹配彩色计算机屏幕 媒体特性 min-device-width 和 max-device-width:匹配设备屏幕的尺寸 min-width 和 max-width:匹配视口的宽度,例如浏览器窗口宽度 orientation:匹配设备是横屏还是竖屏。 <link>标签的media属性通过在<link>标签的media属性中指定条件,有选择的加载样式表。 12<link rel="stylesheet" media="print" href="css/print_styles.css" /><link rel="stylesheet" media="screen and (max-width:568px)" href="css/ipone_styles.css" /> 附录条件注释小于等于IE8加载123<!--[if lte IE 8]> <!-- IE 条件注释 --><link src="ie_only.css" rel="stylesheet" /><![endif]-->]]></content>
<tags>
<tag>CSS 笔记</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS红宝书·读书笔记]]></title>
<url>%2F2017%2F04%2F27%2FJS%E7%BA%A2%E5%AE%9D%E4%B9%A6%C2%B7%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%2F</url>
<content type="text"><![CDATA[花了半个多月的时间,终于又把“JS红宝书”又撸了一遍。 第一次读“JS红宝书”还是2015年初学JS的时候,那时候只是把语法部分读了一遍,还有一些浏览器相关知识做了下了解,大概也就读了半本的样子,就开始了用JS进行开发了,在成长的道路上遇见了JQuery,当时真的是感觉到JQuery太友好了,慢慢放下了原生开发。 现在呢,更多的时候是在用框架进行开发,越来越觉得自己的JS基础很缺乏,然后就开启了“JS红宝书”二刷之路。 下面就把书中自己觉得重要的、没有掌握的知识整理出来。因为我觉得还是会三刷“JS红宝书”,希望把这本700多页的书越读越薄,勉励。 在HTML中使用JavaScript async加载外部脚本文件,通知浏览器立即下载,异步执行。 noscript元素noscript标签显示条件: 浏览器不支持脚本 浏览器支持脚本,但是脚本被禁用 基本概念 语法标识符 第一个字符必须是一个字母、下划线或者一个美元符号 其他字符可以是字母、下划线、美元或者数字 严格模式支持严格模式的浏览器包括:IE10+、Firefox4+、Safari 5.1+、Opera 12+和Chrome。 数据类型undefined对未初始化的变量执行typeof操作会返回undefined值,而对于未声明的变量执行typeof操作同样会返回undefined值。 null1typeof null // -> object undefined值派生自null值。1console.log(null == undefind) // -> true isFinite()测试一个数值是不是无穷值。 Number.NEGATIVE_INFINITY:负无穷Number.POSITION_INFINITY:正无穷 NaN在ECMAScript中,任何数值除以0会返回NaN。 isNaN()接受一个参数,确定这个参数是否”不是数值”。 数值转换Number() 如果是null,返回0 如果是undefined,返回NaN parseInt()在ES5 中不支持解析八进制的能力。1parseInt('070'); // -> 70 not 56 通过第二个参数,指定转换基数(进制)默认十进制。 字符串 ECMAScript中的字符串是不可变的 toString() 在调用数值的toString方法,可以传递一个参数:输出数值的基数。没有toString方法的则返回该值的字面量12var num = 10;console.log(num.toString(2)); // -> '1010' object类型Object类型的属性方法: constructor hasOwnProperty(propertyName) isPrototypeOf(obj) propertyIsEnumerable(propertyName) toLocalString() toString() valueOf() 操作符 ++ or -- 前置与后置的区别123456789var num1 = 2;var num2 = 20;var num3 = --num1 + num2; // 21var num4 = num1 + num2; // 21var num5 = 2;var num6 = 20;var num7 = num5-- + num6; // 22var num8 = num5 + num6; // 21 一元加操作符用于强制类型转换,隐式Number()效果 for-in 语句for-in语句是一种精确的迭代语句,可以用来枚举对象的属性。 通过for-in循环输出的属性名的顺序是不可预测的。 如果要迭代的对象的变量值为null或undefined,for-in语句会抛出错误。ES5更正了这一行为,不再抛出错误,只是不再执行循环体。 建议:在是使用for-in循环之前,先检查对象值是不是null或者undefined。 变量、作用域和内存问题 基本类型和引用类型复制变量值 复制基本类型值,这两个变量相互独立,互不影响。 复制引用类型(对象),值引用是一个指针,改变其中一个对象,会影响另一个对象。 传递参数123456function setName(obj) { obj.name = "Nicholas";}var person = new Object();setName(person);alert(person.name); //"Nicholas" 以上代码中创建一个对象,并将其保存在了变量person 中。然后,这个变量被传递到setName()函数中之后就被复制给了obj。在这个函数部,obj 和person 引用的是同一个对象。换句话说,即使这个变量是按值传递的,obj 也会按引用来访问同一个对象。于是,当在函数内部为obj 添加name属性后,函数外部的person 也将有所反映;因为person 指向的对象在堆内存中只有一个,而且是全局对象。有很多开发人员错误地认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。为了证明对象是按值传递的,我们再看一看下面这个经过修改的例子:12345678function setName(obj) { obj.name = "Nicholas"; obj = new Object(); obj.name = "Greg";}var person = new Object();setName(person);alert(person.name); //"Nicholas" 这个例子与前一个例子的唯一区别,就是在setName()函数中添加了两行代码:一行代码为obj重新定义了一个对象,另一行代码为该对象定义了一个带有不同值的name属性。在把person传递给setName()后,其name 属性被设置为”Nicholas”。然后,又将一个新对象赋给变量obj,同时将其name属性设置为”Greg”。如果person 是按引用传递的,那么person 就会自动被修改为指向其name属性值为”Greg”的新对象。但是,当接下来再访问person.name 时,显示的值仍然是”Nicholas”。这说明即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。 检测类型虽然在检测基本数据类型时typeof 是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大。通常,我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。为此,ECMAScript提供了instanceof 操作符。 延长作用域 try-catch语句中的catch块 with语句 小结 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中; 从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本; 引用类型的值是对象,保存在堆内存中; 包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针; 引用类型 Array类型检测数组123if (value instanceof Array) {} ECMAScript5新增了 Array.isArray()方法123if (Array.isArray(value)) {} sort方法该方法有缺陷,sort()方法会调用每个数组项的toString()转型方法,然后比较字符串进行排序。123var values = [0, 1, 5, 10, 15];values.sort();alert(values); //0,1,10,15,5 因此sort()方法接受一个比较函数作为参数。12345678910111213function compare(value1, value2) { if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; }}var values = [0, 1, 5, 10, 15];values.sort(compare);alert(values); //0,1,5,10,15 splice方法splice方法始终返回一个数组,该数组包含了从原始数组中删除的项。 123456789101112var colors = ["red", "green", "blue"];var removed = colors.splice(0,1); // 删除第一项alert(colors); // green,bluealert(removed); // red,返回的数组中只包含一项removed = colors.splice(1, 0, "yellow", "orange"); // 从位置1 开始插入两项alert(colors); // green,yellow,orange,bluealert(removed); // 返回的是一个空数组removed = colors.splice(1, 1, "red", "purple"); // 插入两项,删除一项alert(colors); // green,red,purple,orange,bluealert(removed); // yellow,返回的数组中只包含一项 迭代方法ECMAScript5为数组定义了5个迭代方法。 every(): 对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。 filter(): 对数组中的每一项运行给定函数,返回该函数会返回true 的项组成的数组。 forEach(): 对数组中的每一项运行给定函数。这个方法没有返回值。 map(): 对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。 some(): 对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。 归并方法ECMAScript 5 还新增了两个归并数组的方法。 reduce() reduceRight()reduce()和reduceRight()的函数接收4 个参数:前一个值、当前值、项的索引和数组对象。12345var values = [1, 2, 3, 4, 5];var sum = values.reduce((prev, cur, index, array) => { return prev + cur;});console.log(sum); RegExp类型正则表达式中的元字符( [ { \ ^ $ | ) ? * + . ] } 注:匹配元字符必须转义 RegExp 构造函数接受两个参数: 一个是要匹配的字符串模式,另一个是可选的标志字符串。123var pattern1 = /[bc]at/i;// 等价于var pattern2 = new RegExp('[bc]at', 'i'); 注:由于RegExp构造函数的模式是字符串,所以在某些情况下要对字符串进行双重转义,所有元字符都必须双重转义。例如\n在字符串中被转义为\\n,而在正则表达式字符串中就会变成\\\\n。 RegExp实例方法exec()方法该方法是专门为捕获组而设计的。exec()接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回null。返回的数组虽然是Array 的实例,但包含两个额外的属性:index 和input。其中,index 表示匹配项在字符串中的位置,而input 表示应用正则表达式的字符串。在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组只包含一项)。123456789var text = "mom and dad and baby";var pattern = /mom( and dad( and baby)?)?/gi;var matches = pattern.exec(text);alert(matches.index); // 0alert(matches.input); // "mom and dad and baby"alert(matches[0]); // "mom and dad and baby"alert(matches[1]); // " and dad and baby"aler t(matches[2]); // " and baby" 对于exec()方法而言,即使在模式中设置了全局标志(g),它每次也只会返回一个匹配项。在不设置全局标志的情况下,在同一个字符串上多次调用exec()将始终返回第一个匹配项的信息。而在设置全局标志的情况下,每次调用exec()则都会在字符串中继续查找新匹配项。 test()方法接受一个字符串参数。在模式与该参数匹配的情况下返回true;否则,返回false。123456var text = "000-00-0000";var pattern = /\d{3}-\d{2}-\d{4}/;if (pattern.test(text)){ alert("The pattern was matched.");} RegExp实例继承的toLocaleString()和toString()方法都会返回正则表达式的字面量,与创建正则表达式的方式无关。123var pattern = new RegExp("\\[bc\\]at", "gi");alert(pattern.toString()); // /\[bc\]at/gialert(pattern.toLocaleString()); // /\[bc\]at/gi Function类型函数声明与函数表达式解析器会率先读取函数声明,并使其在执行任何代码之前可用;至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。1234567891011// okalert(sum(10,10));function sum(num1, num2){ return num1 + num2;}// unexpected identifier(意外标识符)alert(sum(10,10));var sum = function(num1, num2){ return num1 + num2;}; 注:要访问函数的指针而不执行函数的话,必须去掉函数名后的那对圆括号。 函数内部属性 arguments this arguments具有一个callee属性,该属性是一个指针,指向拥有这个arguments对象的函数。1234567function factorial(num){ if (num <=1) { return 1; } else { return num * factorial(num-1) }} 等价于1234567function factorial(num){ if (num <=1) { return 1; } else { return num * arguments.callee(num-1) }} 达到一种解耦的效果。 ECMAScript 5也规范了一个函数对象属性:caller(看着很像callee),这个属性中保存着调用当前函数的函数的引用,如果实在全局作用域中调用当前函数,它的值为null。1234567function outer(){ inner();}function inner(){ alert(inner.caller);}outer(); inner.caller指向outer()。为了实现更松散的耦合,也可以通过argument.callee.caller来访问相同的信息。1234567function outer() { inner();}function inner() { alert(arguments.callee.caller);}outer(); 注:当函数在严格模式下运行时,访问arguments.callee 会导致错误。ECMAScript 5 还定义了arguments.caller属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是undefined。定义这个属性是为了分清arguments.caller 和函数的caller 属性。以上变化都是为了加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。严格模式还有一个限制:不能为函数的caller 属性赋值,否则会导致错误。 函数属性和方法每个函数都包含两个属性: length: 表示函数希望接收的命名参数的个数 prototype: 保存实例方法 每个函数都包含两个非继承而来的方法: apply() call()这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this 对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array 的实例,也可以是arguments对象。call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()方法而言,第一个参数是this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来。 注:在严格模式下,未指定环境对象而调用函数,则this 值不会转型为window。除非明确把函数添加到某个对象或者调用apply()或call(),否则this 值将是undefined。 在非严格模式下,call、apply的第一个参数传递为null或undefined时,函数体内的this会指向默认的宿主对象,在浏览器中则是window。 ECMAScript 5定义了一个方法bind(),这个方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。 基本包装类型使用new调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的。123456var value = '25';var number = Number(value); // 转型函数console.log(typeof number); // 'number'var obj = new Number(value); // 构造函数console.log(typeof obj); // 'object' Number类型Number类型的toString()方法很特别,可以传递一个表示基数的参数。123456var num = 10;alert(num.toString()); //"10"alert(num.toString(2)); //"1010"alert(num.toString(8)); //"12"alert(num.toString(10)); //"10"alert(num.toString(16)); //"a" String类型字符方法 charAt() charCodeAt() 1234var stringValue = "hello world";alert(stringValue.charAt(1)); //"e"alert(stringValue.charCodeAt(1)); //输出字符编码"101" 字符串操作方法 concat() slice() substr() substring() 这些方法对原字符均没有任何影响。 12345678910111213141516171819202122var stringValue = "hello ";var result = stringValue.concat("world", "!");alert(result); //"hello world!"var stringValue = "hello world";alert(stringValue.slice(3)); //"lo world"alert(stringValue.substring(3)); //"lo world"alert(stringValue.substr(3)); //"lo world"alert(stringValue.slice(3, 7)); //"lo w"alert(stringValue.substring(3,7)); //"lo w"alert(stringValue.substr(3, 7)); //"lo worl"// 参数是负值的情况下,它们的行为就不尽相同了。// 其中,slice()方法会将传入的负值与字符串的长度相加,// substr()方法将负的第一个参数加上字符串的长度,而将负的第二个参数转换为0。// 最后,substring()方法会把所有负值参数都转换为0。alert(stringValue.slice(-3)); //"rld"alert(stringValue.substring(-3)); //"hello world"alert(stringValue.substr(-3)); //"rld"alert(stringValue.slice(3, -4)); //"lo w"alert(stringValue.substring(3, -4)); //"hel"alert(stringValue.substr(3, -4)); //""(空字符串) 字符串位置方法 indexOf() lastIndexOf() 两个方法的第二个参数,表示从字符串中哪个位置开始搜索。 trim()方法ECMAScript 5方法 字符串转换大小写方法 toLowerCase() toLocaleLowerCase() toUpperCase() toLocaleUpperCase() 字符串的模式匹配方法 match() search() replace() split() match()方法,在字符串上调用这个方法,本质上和调用RegExp的exec()方法相同。match()方法只接受一个参数,要么是一个正则表达式,要么是一个RegExp对象。12345678var text = 'cat, bat, sat, fat';var pattern = /.at/;// 等价于 pattern.exec(text)var matches = text.match(pattern);alert(matches.index); //0alert(matches[0]); //"cat"alert(pattern.lastIndex); //0 search()方法的参数与match()方法相同,该方法返回字符串中第一个匹配项的索引,没有匹配项返回-1;个人认为serch()就是正则版的indexOf()。123var text = "cat, bat, sat, fat";var pos = text.search(/at/);aler t(pos); //1 ECMAScript提供了replace()方法,该方法接受两个参数,第一个参数可以是RegExp对象或者是一个字符串,第二个参数可以是一个字符串或者一个函数。123456var text = "cat, bat, sat, fat";var result = text.replace("at", "ond");alert(result); //"cond, bat, sat, fat"result = text.replace(/at/g, "ond");aler t(result); //"cond, bond, sond, fond" 字符序列 替换文本 $$ $ $& 匹配整个模式的子字符串。RegExp.lastMatch $' 匹配子字符串之前的字符串。RegExp.leftContext $` 匹配的子字符串之后的字符串。 RegExp.rightContext $n 匹配第n个捕获组的子字符串 n: 0~9 $nn 匹配第nn个捕获组的子字符串 nn: 01~99 123var text = "cat, bat, sat, fat";result = text.replace(/(.at)/g, "word ($1)");alert(result); //word (cat), word (bat), word (sat), word (fat) split()方法可以基于指定的分隔符(字符串 or RegExp对象)将一个字符串分割成多个子字符串,并将结构放在一个数组中。可以接受可选的第二个参数,用于指定数组的大小。1234var colorText = "red,blue,green,yellow";var colors1 = colorText.split(","); //["red", "blue", "green", "yellow"]var colors2 = colorText.split(",", 2); //["red", "blue"]var colors3 = colorText.split(/[^\,]+/); //["", ",", ",", ",", ""] localeCompare()方法比较两个字符串,并返回下列值中的 一个: 如果字符串在字母表中应该排在字符串参数之前,则返回一个负数 如果字符串等于字符串参数,则返回0; 如果字符串在字母表中应该排在字符串参数之后,则返回一个正数 1234var stringValue = "yellow";alert(stringValue.localeCompare("brick")); //1alert(stringValue.localeCompare("yellow")); //0alert(stringValue.localeCompare("zoo")); //-1 fromCharCode()方法这个方法的任务是接收一个或多个字符编码,然后将它们转换成一个字符串。相当于charCodeAt()反操作。1alert(String.fromCharCode(104, 101, 108, 108, 111)); //"hello" Math 对象 min() max() ceil() floor() round() random() 面向对象的程序设计理解对象属性类型数据类型 [[Configurable]]: 表示能否通过delete删除属性从而重新定义属性,能够修改属性的特性,或者能否把属性修改为访问器属性 [[Enumerable]]: 表示能否通过for-in循环返回属性 [[Writable]]: 表示能否修改属性的值 [[Value]]: 包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。默认值undefined 要修改属性默认的特性,必须使用ECMAScript 5的Object.defineProperty()方法。这个方法接受三个参数:属性所在对象,属性名和一个描述符对象。其中描述符对象的属性值必须是:configurable、enumerable、writable和value。设置其中一个或多个。12345var person = {};Object.defineProperty(person, 'name', { writable: false, value: 'Yeaseon'}); Object.defineProperty()方法不能对configurable: false的对象进行修改。 访问器属性 [[Configurable]]: 表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。 [[Enumerable]]: 表示能否通过for-in循环返回属性。 [[Get]]: 在读取属性时调用的函数,默认undefined [[Set]]: 在写入属性时调用的函数,默认undefined 1234567891011121314151617var book = { _year: 2004, edition: 1};Object.defineProperty(book, "year", { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } }});book.year = 2005;alert(book.edition); //2 读取属性的特性ECMAScript 5的Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。该方法接收两个参数:属性所在的对象和要读取器描述符的属性名称,返回值是对象。 创建对象工厂模式123456789101112function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o;}var person1 = createPerson("Nicholas", 29, "Software Engineer");var person2 = createPerson("Greg", 27, "Doctor"); 构造函数模式12345678910function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); };}var person1 = new Person("Nicholas", 29, "Software Engineer");var person2 = new Person("Greg", 27, "Doctor"); 原型模式1234567891011function Person(){}Person.prototype.name = "Nicholas";Person.prototype.age = 29;Person.prototype.job = "Software Engineer";Person.prototype.sayName = function(){ alert(this.name);};var person1 = new Person();person1.sayName(); //"Nicholas" Person.prototype.constructor会指向Person,person1并没有直接连接到构造函数Person。 可以通过isPrototypeOf()方法来确定对象之间是否存在原型关系。从本质上讲,[[Prototype]]指向调用isPrototypeOf()方法的对象Person.prototype,则会返回true。 1alert(Person.prototype.isPrototypeOf(person1)); //true 在ECMAScript 5增加了Object.getPrototypeOf()方法,该方法返回[[Prototype]]的值。12alert(Object.getPrototypeOf(person1) == Person.prototype); //truealert(Object.getPrototypeOf(person1).name); //"Nicholas" 注: 虽然可以通过对象实例person1访问保存在原型中的值,但却不能重写原型中的值。123456789101112131415function Person(){}Person.prototype.name = "Nicholas";Person.prototype.age = 29;Person.prototype.job = "Software Engineer";Person.prototype.sayName = function(){ alert(this.name);};var person1 = new Person();var person2 = new Person();person1.name = "Greg"; //实质是在实例上增加一个name属性alert(person1.name); //"Greg"——来自实例alert(person2.name); //"Nicholas"——来自原型 可以通过delete删除实例属性,从而继续访问原型中的属性。123456789101112131415function Person(){}Person.prototype.name = "Nicholas";Person.prototype.age = 29;Person.prototype.job = "Software Engineer";Person.prototype.sayName = function(){ alert(this.name);};var person1 = new Person();person1.name = "Greg";alert(person1.name); //"Greg"——来自实例delete person1.name;alert(person1.name); //"Nicholas"——来自原型 hasOwnProperty()方法可以检测一个属性是不是存在于实例,是则返回true。 in操作符(prop in obj)通过in操作符可以判定对象是否有该属性,不论是本身含有还是原型含有,都返回true。可以通过in配合hasOwnProperty()确定该属性是存在于对象中还是原型中: 1234567function detectProperty(obj, name) { if (name in obj) { obj.hasOwnProperty(name) ? '在对象中' : '在原型中'; } else { console.log('不含有该属性'); }} ECMAScript 5Object.keys()方法可以取得对象上所有可枚举的实例属性。 12345678910111213141516function Person(){}Person.prototype.name = "Nicholas";Person.prototype.age = 29;Person.prototype.job = "Software Engineer";Person.prototype.sayName = function(){ alert(this.name);};var keys = Object.keys(Person.prototype);alert(keys); //"name,age,job,sayName"var p1 = new Person();p1.name = "Rob";p1.age = 31;var p1keys = Object.keys(p1);alert(p1keys); //"name,age" Object.getOwnPropertyNames会得到所有实例属性,不论是否可枚举。 12var keys = Object.getOwnPropertyNames(Person.prototype);alert(keys); //"constructor,name,age,job,sayName" 简化Person.prototype写法:12345678910function Person(){}Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); }}; 这样写有一个缺陷,constructor属性则会等于Object,我们需要手动设置constructor。1234567891011function Person(){}Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); }}; 但这同时也会导致constructor的[[Enumerable]]特性变成了true,默认情况下是false。再修改下写法:123456789101112131415function Person(){}Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); }};Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person}); 原型重写会导致构造函数与最初原型之间的联系切断。12345678910111213function Person(){}var friend = new Person();Person.prototype = { //重写 constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); }};friend.sayName(); //error 结合使用构造函数和原型模式用构造函数模式定义实例属性,用原型模式定义方法和共享属性。1234567891011121314151617181920function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"];}Person.prototype = { constructor : Person, sayName : function(){ alert(this.name); }}var person1 = new Person("Nicholas", 29, "Software Engineer");var person2 = new Person("Greg", 27, "Doctor");person1.friends.push("Van");alert(person1.friends); //"Shelby,Count,Van"alert(person2.friends); //"Shelby,Count"alert(person1.friends === person2.friends); //falsealert(person1.sayName === person2.sayName); //true 继承构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型的内部指针。 原型链1234567891011121314151617181920function SuperType () { this.property = true;}SuperType.prototype.getSuperValue = function () { return this.property;};function SubType () { this.subproperty = false;}SubType.prototype = new SuperType();SubType.prototype.getSubValue = function () { return this.subproperty;};var instance = new SubType();console.log(instance.getSuperValue()); // true instanceof操作符用来确定原型和实例之间的关系。123alert(instance instanceof Object); //truealert(instance instanceof SuperType); //truealert(instance instanceof SubType); //true 第二种方式就是isPrototypeOf()方法,只要原型链中出现过的原型,都可以说是该原型链所派生的实例的原型。123alert(Object.prototype.isPrototypeOf(instance)); //truealert(SuperType.prototype.isPrototypeOf(instance)); //truealert(SubType.prototype.isPrototypeOf(instance)); //true 函数表达式由于有声明提升的存在,定义函数不要放在条件表达式中123456789if (condition) { function sayHi () { console.log('Hi'); }} else { function sayHi () { console.log('Yo'); }} 在ECMAScript中属于无效语法,在不同浏览器中修正的做法并不一致。推荐的写法,如下:1234567891011var sayHi;if (condition) { sayHi = function () { console.log('Hi'); }} else { sayHi = function () { console.log('Yo'); }} 这种函数表达式不存在声明提升,所以OK。 递归函数作用域链当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象。在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位…,直到作用域终点的全局执行环境。 闭包123456789function createFunctions () { var result = new Array(); for (var i=0; i < 10; i++){ result[i] = function(){ return i; }; } return result;} 1234567891011function createFunction () { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function (num) { return function () { return num; }; }(i); } return result;} 注:在闭包中使用this对象可能会导致一些问题。匿名函数的执行环境具有全局性,因此其this对象通常指向window。123456789101112var name = 'The window';var obj = { name: 'my object', getNameFunc: function () { return function () { return this.nam; } }}console.log(obj.getNameFunc()()); // The Window (非严格模式) 模仿块级作用域块级作用域123(function () { // 这里是块级作用域})(); BOM window 对象全局作用域抛开全局变量会成为window对象的属性不谈,定义全局变量与在window对象上直接定义属性还是有一点差别:全局变量不能通过delete操作符删除,而直接定义在window对象上的定义的属性可以。 窗口位置获得窗口左边和上边的位置。12var leftPos = (typeof window.screenLeft == 'number') ? window.screenLeft : window.screenX;var topPos = (typeof window.screenTop == 'number') ? window.screenTop : window.screenY; Firefox支持screenX和screenY,其他浏览器均支持screenLeft、screenTop。 但是还是需要注意一个问题:在IE Opera中,screenLeft screenTop 保存的的是可见区域的距离,也就是我们浏览器中不包含工具栏的区域与屏幕的距离;在Chrome、Firefox和Safari中screenY或screenTop返回的是整个浏览器窗口相对于屏幕坐标的值。 窗口大小IE9+、Firefox、Safari、Opera和Chrome均提供了4个属性innerWidth、innerHeight、outerWidth和outerHeight。 IE9+、Safari和Firefox中,outerWidth和outerHeight返回浏览器窗口本身的尺寸,而innerWidth和innerHeight则表示该容器中页面视图区的大小(减去边框宽度) Chrome中,inner*和outer*返回相同的值,即视口大小而非浏览器窗口的大小。 在IE、Firefox、Safari、Opera和Chrome中,都能通过document.documentElement.clientWidth和document.documentElement.clientHeight中保存了页面视口信息。 获取页面视口大小123456789101112var pageWidth = window.innerWidth, pageHeight = window.innerHeight;if (typeof pageWidth != 'number') { if (document.compatMode == 'CSS1Compat') { // 浏览器标准模式 pageWidth = document.documentElement.clientWidth; pageHeight = document.documentElement.clientHeight; } else { // IE6 混杂模式 pageWidth = document.body.clientWidth; pageHeight = document.doby.clientHeight; }} resizeTo()接受浏览器窗口的新宽度和新高度 resizeBy()接受新窗口与原窗口的宽度和高度差。 这两个方法可能被浏览器禁用。 导航如果是浏览器内置的屏蔽程序组织的弹出窗口,那么window.open()很可能会返回null。1234var newWindow = window.open('https://www.google.com.hk', '_blank');if (newWindow == null) { console.log('The popup was blocked!');} 如果是浏览器扩展或其他程序组织的弹出窗口,那么window.open()通常会抛出一个错误。1234567891011121314var blocked = false;try { var newWindow = window.open('https://www.google.com.hk', '_blank'); if (newWindow == null) { blocked = true; }} catch (ex) { blocked = true;}if (blocked) { console.log('The popup was blocked');} location 对象location对象的属性 hash host hostname:与host不同的是,不带端口号 href pathname: 返回URL中的目录和(或)文件名 port protocol search:返回URL的查询字符串,这个字符串?开头 navigator 对象location对象的属性 appCodeName: 浏览器的名称,通常都是Mozilla appMinorVersion:此版本信息 appName: 完整的浏览器名称 appVersion:浏览器的版本 buildID:浏览器编译版本 cookieEnabled:表示cookie是否可用 cpuClass:客户端计算机中使用的CPU类型 javaEnabled():表示当前浏览器中是否启用了java language: 浏览器的主语言 mimeTypes:浏览器中注册的MIME类型数组 onLine:表示浏览器是都连接到因特网 oscpu:客户端计算机的操作系统或使用的CPU platform:浏览器所在的系统平台 plugins:浏览器中安装的插件信息的数组 preference():设置用户的首选项 systemLanguage:操作系统的语言 userAgent:浏览器的用户代理字符串 DOM 节点层次Node类型每个节点都有一个nodeType属性,用于表明节点的类型。 Node.ELEMENT_NODE(1) Node.ATTRIBUTE_NODE(2) Node.TEXT_NODE(3) Node.CDATA_SECTION_NODE(4) Node.ENTITY_REFERENCE_NODE(5) Node.ENTITY_NODE(6) Node.PROCESSING_INSTRUCTION_NODE(7) Node.COMMENT_NODE(8) Node.DOCUMENT_NODE(9) Node.DOCUMENT_TYPE_NODE(10) Node.DOCUMENT_FRAGMENT_NODE(11) Node.NOTATION_NODE(12) 为了确保跨浏览器兼容,将nodeType属性与数字值进行比较:123if (someNode.nodeType == 1) { console.log('Node is an element');} nodeName属性123if (someNode.nodeType == 1) { var value = someNode.nodeName; // nodeName的值是元素的标签名} 节点关系 childNodes属性 每个节点都有一个childNodes属性,其中保存着一个NodeList对象,该对象是一种类数组对象。 parentNode属性 每个节点都有一个parentNode属性,该属性指向文档树中的父节点。包含在childNodes列表中的每个节点相互都是兄弟节点。使用previousSibling和nextSibling属性,可以访问其他兄弟节点。 注:列表中第一个节点的previousSibling属性值为null,同理列表中最后一个节点的nextSibling属性也是null。父节点的firstChild和lastChild属性分别指向其childNodes列表中的第一个和最后一个节点。如果不存在则为null。 hasChildNodes()方法在节点包含一个或多个子节点的情况下返回true,比查询childNodes.length更简便。 最后一个属性ownerDocument,该属性指向表示整个文档的文档节点(root),直接返回根节点不需要一层层向上回溯。 操作节点 appendChild() 用于向childNodes列表的末尾添加一个节点。123var returnedNode = someNode.appendChild(newNode);alert(returnedNode == newNode); //truealert(someNode.lastChild == newNode); //true 任何DOM节点不可能同时出现在多个位置。1234//someNode 有多个子节点var returnedNode = someNode.appendChild(someNode.firstChild);alert(returnedNode == someNode.firstChild); //falsealert(returnedNode == someNode.lastChild); //true insertBefore() 把节点放在指定位置,该方法接受两个参数:要插入的节点和作为参考的节点。插入节点后,被插入的节点会变成参照节点的前一个兄弟节点。参照节点是null的话,insertBefore与appendChild执行相同的操作,都插入列表末尾。123456789101112//插入后成为最后一个子节点returnedNode = someNode.insertBefore(newNode, null);alert(newNode == someNode.lastChild); //true//插入后成为第一个子节点var returnedNode = someNode.insertBefore(newNode, someNode.firstChild);alert(returnedNode == newNode); //truealert(newNode == someNode.firstChild); //true//插入到最后一个子节点前面returnedNode = someNode.insertBefore(newNode, someNode.lastChild);alert(newNode == someNode.childNodes[someNode.childNodes.length-2]); //true replaceChild() 替换节点,接受两个参数:要插入的节点和要替换的节点。12345//替换第一个子节点var returnedNode = someNode.replaceChild(newNode, someNode.firstChild);//替换最后一个子节点returnedNode = someNode.replaceChild(newNode, someNode.lastChild); removeChild() 移除节点,接受一个参数:要被移除的节点。12345//移除第一个子节点var formerFirstChild = someNode.removeChild(someNode.firstChild);//移除最后一个子节点var formerLastChild = someNode.removeChild(someNode.lastChild); cloneNode() 复制节点,接受一个布尔值,表示是否深复制。复制后返回的节点没有父节点,可以通过插入等操作手动指定。12345var deepList = myList.cloneNode(true);alert(deepList.childNodes.length); //3(IE < 9)或7(其他浏览器)var shallowList = myList.cloneNode(false);alert(shallowList.childNodes.length); //0 注:cloneNode方法不会复制DOM节点的js属性。IE存在一个bug,它会复制事件处理程序。 normalize() 稍后讨论 以上方法的返回值,都是被操作的节点。 Document类型Document节点具有下列特征: nodeType的值为9 nodeName的值为#document nodeValue的值为null parentNode的值为null ownerDocument的值为null 其子节点可能是一个DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction或Comment 1234567891011121314// 通过`documentElement`属性访问<html>元素var html = document.documentElement;// 访问 <body> 元素var body = document.body;// <!DOCTYPE>var doctype = document.doctype;// <title>var title = document.title;// 完整 urlvar url = document.URL;// domain 域名var domain = document.domain;// 取得来源页面的URL(也就是导航到这页的页面)var referrer = document.referrer; 查找元素的方法: document.getElementById() document.getElementsByTagName() document.getElementsByName() 文档写入: document.write() document.writeln()在字符串尾加换行符(\n) 123<script type="text/javascript"> document.write("<script type=\"text/javascript\" src=\"file.js\">" + "<\/script>");</script> Element类型Element类型提供了对元素标签名、子节点及特性的访问。 nodeType的值为1 nodeName的值为元素的标签名 nodeValue的值为null parentNode可能是Document或Element 其子节点可能是Element、Text、Comment、ProcessingInstruction、CDATASection或EntityReference 访问元素的标签名,可以使用nodeName属性,也可以使用tagName属性,后者更直观。12345<div id="myDiv"></div>var div = document.getElementById("myDiv");alert(div.tagName); //"DIV"alert(div.tagName == div.nodeName); //true 操作特性的方法: getAttribute() setAttribute() removeAttribute() attributes属性 Element类型是使用attributes属性的唯一一个DOM节点属性。attributes属性包含一个NamedNodeMap。元素的每一个特性都由一个Attr节点表示,每个节点都保存在NamedNodeMap对象中。 NamedNodeMap对象的方法: getNamedItem(name):返回nodeName属性等于name的节点 removeNamedItem(name):从列表中移除nodeName属性等于name的节点 setNamedItem(node):向列表中添加节点,以节点的nodeName属性为索引 item(pos):返回位于数字pos位置处的节点 attributes属性中包含一系列节点,每个节点的nodeName就是特性的名称,而节点nodeValue就是特性的值。123var id = element.attributes.getNamedItem('id').nodeValue;// 简写var id = element.attributes['id'].nodeValue; 创建元素document.createElement()方法可以创建新元素,这个方法接受一个参数(标签名)1var div = document.createElement('div'); Text类型文本节点由Text类型表示,包含的是可以照字面解释的纯文本内容。纯文本中可以包含转义后的HTML字符,但不能包含HTML代码。 nodeType的值为3 nodeName的值为#text nodeValue的值为节点所包含的文本 parentNode是一个Element 操作节点中的文本: appendData(text):将text添加到节点的末尾 deleteData(offset, count):从offset指定的位置开始删除count个字符 insertData(offset, text):在offset指定的位置插入text replaceData(offset, count, text):用text替换从offset指定的位置开始到offset+count为止的文本 splitText(offset):从offset指定的位置将当前文本分成两个文本节点 substringData(offset, count):提取从offset指定的位置开始到offset+count为止处的字符串。 在向DOM文档中插入文本之前,应该先对其进行HTML编码 创建文本节点 document.createTextNode() 1var textNode = document.createTextNode("<strong>Hello</strong> world!"); DOM 操作技术使用函数实现加载外部JS文件1234567function loadScript(url) { var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; document.body.appendChild(script);}loadScirpt('xx.js'); IE将<script>视为一个特殊的元素,不允许DOM访问其子节点。不过可以使用<script>元素的text属性指定JS代码。 操作表格12345678910111213141516171819202122232425262728293031323334// create tablevar table = document.createElement('table');table.border = 1;table.width = '100%';// create tbodyvar tbody = document.createElement('tbody');table.appendChild(tbody);// create row1var row1 = document.createElement('tr');tbody.appendChild(row1);var cell1_1 = document.createElement('td');cell1_1.appendChild(document.createTextNode('Cell 1,1'));row1.appendChild(cell1_1);var cell2_1 = document.createElement('td');cell2_1.appendChild(document.createTextNode('Cell 2,1'));row1.appendChild(cell2_1);// create row2var row2 = document.createElement('tr');tbody.appendChild(row2);var cell1_2 = document.createElement('td');cell1_2.appendChild(document.createTextNode('Cell 1,2'));row1.appendChild(cell1_2);var cell2_2 = document.createElement('td');cell2_2.appendChild(document.createTextNode('Cell 2,2'));row1.appendChild(cell2_2);document.body.appendChild(table); DOM 扩展 选择符 API querySelector()方法 querySelector()方法接受一个CSS选择符,返回与该模式匹配的第一个元素,若没有,返回null。 可以通过Document类型调用,也可以通过Element类型调用,后者只会在该元素后代元素的范围内查找匹配的元素。 querySelectorAll()方法 querySelectorAll()方法返回的是所有匹配的元素,是一个NodeList实例。 matchesSelector()方法 为Element类型新增的一个方法,接受一个参数CSS选择符,如果调用元素与该选择符匹配,返回true,否则返回false。 元素遍历 childElementCount:返回子元素(不包含文本节点和注释)的个数 firstElementChild:指向第一个子元素 lastElementChild:指向最后一个子元素 previousElementSibling:指向前一个兄弟元素 nextElementSibling:指向后一个兄弟元素 不同于前面的返回节点的方法。123456789101112131415161718// 节点版本var i, len, child = element.firstChild;while(child != element.lastChild){ if (child.nodeType == 1){ //检查是不是元素 processChild(child); } child = child.nextSibling;}// 元素版本var i, len, child = element.firstElementChild;while(child != element.lastElementChild){ processChild(child); //已知其是元素 child = child.nextElementSibling;} HTML5 getElementsByClassName()方法 classList属性,这个属性是新集合类型DOMTokenList的实例。 add(value) contains(value) remove(value) toggle(value) 1div.classList.remove("user"); 焦点管理document.activeElement属性,始终会引用DOM中前端获得了焦点的元素。123var button = document.getElementById("myButton");button.focus();alert(document.activeElement === button); //true document.hasFocus()方法,可以确定文档是否获得了焦点。123var button = document.getElementById("myButton");button.focus();alert(document.hasFocus()); //true HTMLDocument的变化+ `readyState`属性有两个值,`loading`和`complete` 123if (document.readyState == 'complete') { // 加载完成} document.charset字符集属性data-自定义数据属性1234567891011<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>var div = document.getElementById("myDiv");//取得自定义属性的值var appId = div.dataset.appId;var myName = div.dataset.myname;//设置值div.dataset.appId = 23456;div.dataset.myname = "Michael"; innerHTML属性在读模式下,innerHTML 属性返回与调用元素的所有子节点(包括元素、注释和文本节点)对应的HTML 标记。在写模式下,innerHTML 会根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点 outerHTML属性在读模式下,outerHTML 返回调用它的元素及所有子节点的HTML 标签。在写模式下,outerHTML会根据指定的HTML 字符串创建新的DOM 子树,然后用这个DOM子树完全替换调用元素。 insertAdjacentHTML()方法插入元素的新增方法,接受两个参数,插入的位置和要插入的HTML文本,第一个参数的值: 'beforebegin' 'afterbegin' 'beforeend' 'afterend' 1234567891011//作为前一个同辈元素插入element.insertAdjacentHTML("beforebegin", "<p>Hello world!</p>");//作为第一个子元素插入element.insertAdjacentHTML("afterbegin", "<p>Hello world!</p>");//作为最后一个子元素插入element.insertAdjacentHTML("beforeend", "<p>Hello world!</p>");//作为后一个同辈元素插入element.insertAdjacentHTML("afterend", "<p>Hello world!</p>"); scrollIntoView()方法scrollIntoView方法可以在所有HTML元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中。如果这个方法传入true作为参数,或者不传参数,那么窗口滚动之后就会让调用元素的顶部与视口顶部 尽可能平齐,如果传入false,调用元素会尽可能全部出现在视口中,不过顶部不一定平齐。12// 让元素可见document.form[0].scrollIntoView(); 专有扩展插入文本 innerText属性 outerText属性 滚动 scrollIntoViewIfNeeded(alignCenter):只有在当前元素不可见的情况下,才滚动浏览器或窗口或容器元素最终让它可见。如果当前元素在视口中可见,这个方法什么也不做。 scrollByLines(lineCount):将元素的内容滚动指定的行高,lineCount值可以是正值,也可以是负值。 scrollByPages(pageCount):将元素的内容滚动指定的页面高度,具体高度由元素的高度决定。 scrollIntoView()和scrollIntoViewIfNeeded()的作用对象是元素的容器,而scrollByLines()和scrollByPages()影响的则是元素自身。 12345//在当前元素不可见的时候,让它进入浏览器的视口document.images[0].scrollIntoViewIfNeeded();//将页面主体往回滚动1 页document.body.scrollByPages(-1); DOM2 和 DOM3 样式元素大小偏移量 offsetHeight:元素在垂直方向上占用的空间大小。包括元素的高度,(可见的)水平滚动条的高度,上边框高度和下边框高度 offsetWidth:元素在水平方向上占用的空间大小。包括元素的宽度,(可见的)垂直滚动条的宽度,左边框宽度和右边框宽度 offsetLeft:元素的左外边框至包含元素的左内边框之间的像素距离。 offsetTop:元素的上外边框至包含元素的上内边框之间的像素距离。 其中,offsetLeft和offsetTop属性与包含元素有关,包含元素的引用保存在offsetParent属性中。offsetParent属性不一定与parentNode的值相等。123456789101112// 元素上偏移function getElementLeft (ele) { var actualLeft = ele.offsetLeft; var current = ele.offsetParent; while (current !== null) { actualLeft += current.offsetLeft; current = current.offsetParent; } return actualLeft;}// 元素左偏移同理 一般来说,页面中所有的元素都被包含在几个<div>元素中,而这些<div>元素的offsetParent又是<body>元素,所以getElementLeft()与getElementTop()会返回与offsetLeft和offsetTop相同的值。 客户区大小 clientWidth:元素内容区宽度加上左右内边距宽度 clientHeight: 元素内容区高度加上上下内边距高度 12345678910111213function getViewport(){ if (document.compatMode == "BackCompat"){ // IE7之前 return { width: document.body.clientWidth, height: document.body.clientHeight }; } else { return { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight }; }} 滚动大小滚动大小,指的是包含滚动内容的元素的大小。有些元素(<html>),即使没有执行任何代码也能自动添加滚动条;但另外一些元素,则需要通过CSS的overflow属性设置才能滚动。 scrollHeight:在没有滚动条的情况下,元素内容的总高度 scrollWidth:在没有滚动条的情况下,元素内容的总宽度 scrollLeft:被隐藏在内容区域左侧的像素数,通过设置这个属性可以改变元素的滚动位置 scrollTop:被隐藏在内容区域上方的像素数,通过设置这个属性可以改变元素的滚动位置 scrollWidth和scrollHeight主要用于确定元素内容的实际大小。 scrollWidth 和scrollHeight 与clientWidth 和clientHeight 之间的关系? Firefox中这两组属性始终相等,但大小代表的是文档内容区域的实际尺寸,非视口尺寸 Opera Safari Chrome中这两组属性有区别,其中scrollWidth 和scrollHeight 等于视口大小,而clientWidth 和clientHeight 等于文档内容区域的大小。 IE(在标准模式)中的这两组属性不相等,其中scrollWidth 和scrollHeight 等于文档内容区域的大小,而clientWidth 和clientHeight 等于视口大小。 通过scrollLeft 和scrollTop 属性既可以确定元素当前滚动的状态,也可以设置元素的滚动位置。在元素尚未被滚动时,这两个属性的值都等于0。如果元素被垂直滚动了,那么scrollTop 的值会大于0,且表示元素上方不可见内容的像素高度。如果元素被水平滚动了,那么scrollLeft 的值会大于0,且表示元素左侧不可见内容的像素宽度。这两个属性都是可以设置的,因此将元素的scrollLeft 和scrollTop 设置为0,就可以重置元素的滚动位置。下面这个函数会检测元素是否位于顶部,如果不是就将其回滚到顶部。12345function scrollToTop(element){ if (element.scrollTop != 0){ element.scrollTop = 0; }} 确定元素大小 getBoundingClientRect()方法,会返回一个矩形对象,包含left top right bottom四个属性。这些属性给出了元素在页面中相对于视口的位置。 遍历NodeIterator可以使用document.createNodeIterator()方法创建它的新实例,接受4个参数。 root:想要作为搜索起点的树中的节点 whatToShow:表示要访问哪些节点的数字代码 filter:是一个NodeFilter对象,或者一个表示应该接受还是拒绝某种特定节点的函数 entityReferenceExpansion:布尔值,表示是否要扩展实体引用。 whatToShow这个参数的值以常量形式在NodeFilter类型中定义: NodeFilter.SHOW_ALL NodeFilter.SHOW_ELEMENT NodeFilter.SHOW_ATTRIBUTE NodeFilter.SHOW_TEXT NodeFilter.SHOW_CDATA_SECTION NodeFilter.SHOW_ENTITY_REFERENCE NodeFilter.SHOW_ENTITYE NodeFilter.SHOW_PROCESSING_INSTRUCTION NodeFilter.SHOW_COMMENT NodeFilter.SHOW_DOCUMENT NodeFilter.SHOW_DOCUMENT_TYPE NodeFilter.SHOW_DOCUMENT_FRAGMENT NodeFilter.SHOW_NOTATION 事件 事件流事件冒泡IE的事件流叫做事件冒泡,即事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的节点。 事件捕获Netscape 团队提出的事件流叫做事件捕获,事件捕获的用意在于在事件到达预定目标之前捕获它。 DOM事件流“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。 事件处理程序DOM0 级事件处理程序每个元素(包括window和document)都有自己的事件处理程序,这些属性通常全部小写。1234var btn = document.getElementById('myBtn');btn.onclick = function () { console.log('clicked');} DOM 0级方法指定的事件处理程序被认为是元素的方法,因此,这个时候的事件处理程序是在元素的作用域中运行,也就是说程序中的this可以引用当前元素。1234var btn = document.getElementById('myBtn');btn.onclick = function () { console.log(this.id); // 'myBtn'} 以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。 DOM2 级事件处理程序 addEventListener() removeEventListener() 定义了两个方法用于处理指定和删除事件处理程序的操作。所有的DOM节点中都包含这两个方法,接受三个参数:事件名、事件处理程序和布尔值。最后这个布尔值如果是true,表示在捕获阶段调用事件处理程序;false表示在冒泡阶段调用事件处理程序,默认是false。 通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除。如果通过addEventListener()添加的匿名函数将无法移除。123btn.addEventListener('click', function () { //匿名函数 ...}) 注:大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段(false),这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需要,我们不建议在事件捕获阶段注册事件处理程序。 IE事件处理程序 attachEvent() detachEvent() 这两个方法接受两个参数:事件名(带on)和事件处理函数。1234var btn = document.getElementById("myBtn");btn.attachEvent("onclick", function(){ alert("Clicked");}); 注:在IE 中使用attachEvent()与使用DOM0 级方法的主要区别在于事件处理程序的作用域。 DOM0 级作用域是其所属元素 attachEvent()方法的作用域是全局(this === window) 1234567var btn = document.getElementById("myBtn");btn.attachEvent("onclick", function(){ alert("Clicked");});btn.attachEvent("onclick", function(){ alert("Hello world!");}); 注:与DOM方法不同的是,这些事件处理程序不是以添加它们的顺序执行,而是以相反的顺序被触发。单击这个例子中的按钮,首先看到的是”Hello world!”,然后才是”Clicked”。 事件对象在触发DOM上的某个事件时,会产生一个事件对象event。 DOM中的事件对象event对象成员 属性/方法 类型 读/写 说明 bubbles Boolean 只读 表明事件是否冒泡 cancelable Boolean 只读 表明是否可以取消事件的默认行为 currentTarget Element 只读 其事件处理程序当前正在处理事件的那个元素 defaultPrevented Boolean 只读 为true表示已经调用preventDefault() detail Integer 只读 与事件相关的细节信息 eventPhase Integer 只读 调用事件处理程序的阶段:1 捕获,2 处于目标,3 冒泡 preventDefault() Function 只读 取消事件的默认行为。如果cancelable 是true,则可以使用这个方法 stopImmediatePropagation() Function 只读 取消事件的进一步冒泡或捕获,同时阻止任何事件处理程序被调用 stopPropagation() Function 只读 取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法 target Element 只读 事件的目标 trusted Boolean 只读 为true表示事件是浏览器生成,false是开发人员创建 type String 只读 被触发的事件类型 view AbstractView 只读 与事件关联的抽象视图。等同于发生事件的window对象 在事件处理程序内部,对象this 始终等于currentTarget 的值,而target 则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则this、currentTarget 和target 包含相同的值。12345document.body.onclick = function(event){ alert(event.currentTarget === document.body); //true alert(this === document.body); //true alert(event.target === document.getElementById("myBtn")); //true}; 调用event方法1234var link = document.getElementById("myLink");link.onclick = function(event){ event.preventDefault();}; 跨浏览器的事件对象12345678910111213141516171819202122232425262728var EventUtil = { addHandler: function(element, type, handler){ //省略的代码 }, getEvent: function(event){ return event ? event : window.event; }, getTarget: function(event){ return event.target || event.srcElement; }, preventDefault: function(event){ if (event.preventDefault){ event.preventDefault(); } else { event.returnValue = false; } }, removeHandler: function(element, type, handler){ //省略的代码 }, stopPropagation: function(event){ if (event.stopPropagation){ event.stopPropagation(); } else { event.cancelBubble = true; } }}; 事件类型UI 事件 load事件 unload事件 resize事件 scroll事件 焦点事件 blur事件:失去焦点 focus事件:获得焦点 鼠标与滚动事件 click事件 dbclick事件 mousedown事件:按下鼠标 mouseenter事件:光标移入 mouseleave事件:光标移出 mousemove事件:鼠标在元素内部移动重复触发 mouseout事件:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。又移入的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素 mouseover事件:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发 mouseup事件:释放鼠标按钮时触发 页面上的所有元素都支持鼠标事件。除了mouseenter 和mouseleave,所有鼠标事件都会冒泡,也可以被取消,而取消鼠标事件将会影响浏览器的默认行为。 只有在同一个元素上相继触发mousedown 和mouseup 事件,才会触发click 事件;如果mousedown 或mouseup 中的一个被取消,就不会触发click 事件。 触摸设备iOS和Android设备的相关事件: 不支持dbclick事件。双击浏览器窗口会放大画面 轻击可单击元素会触发mousemove事件。。如果此操作会导致内容变化,将不再有其他事件发生;如果屏幕没有因此变化,那么会依次发生mousedown、mouseup 和click 事件。轻击不可单击的元素不会触发任何事件。可单击的元素是指那些单击可产生默认操作的元素(如链接),或者那些已经被指定了onclick 事件处理程序的元素。 mousemove事件也会触发mouseover和mouseout事件 两个手指放在屏幕上且页面随手指移动而滚动时会触发mousewheel和scroll事件。 HTML5事件 contextmenu事件 beforeunload事件 DOMContentLoaded事件 readystatechange事件 uninitialized未初始化 loading loaded interactive:可以操作对象,但还没有完全加载 complete hashchange事件 设备事件 orientationchange事件:横竖屏,有三个值: -90 ,0, 90 触摸与手势事件 触摸事件 touchstart touchmove touchend touchcancel 手势事件 gesturestart gesturechange gestureend 内存和性能事件委托例如在<ul>为添加一个click事件,所有<li>子元素点击事件都会冒泡到<ul>上。 表单脚本 表单基础知识提交表单1<input type="submit" value="Submit Form"> 重置表单1<input type="reset" value="Reset Form"> 表单字段每个表单都有elements属性,该属性是表单中所有表单元素的集合。1234567var form = document.getElementById("form1");//取得表单中的第一个字段var field1 = form.elements[0];//取得名为"textbox1"的字段var field2 = form.elements["textbox1"];//取得表单中包含的字段的数量var fieldCount = form.elements.length; 文本框脚本过滤输入屏蔽特定的字符,需要检测keypress事件对应的字符编码。123456789EventUtil.addHandler(textbox, 'keypress', function (event) { event = EventUtil.getEvent(event); var target = EventUtil.getTarget(event); var charCode = EventUtil.getCharCode(event); if (!/\d/.test(String.fromCharCode(charCode))) { EventUtil.preventDefault(event); }}) HTML5约束验证API输入模式HTML5为文本字段新增了pattern属性。1<input type="text" pattern="\d+" name="count"> 检测有效性使用checkValidity()方法可以检测表单中的某个字段是否有效。是否有效的判断依据是一些<input>的约束条件。12345if (document.forms[0].elements[0].checkValidity()){ //字段有效,继续} else { //字段无效} 也可以检测整个表单是否有效12345if(document.forms[0].checkValidity()){ //表单有效,继续} else { //表单无效} 禁用验证123<form method="post" action="signup.php" novalidate> <!--这里插入表单元素--></form> HTML5 脚本编程 跨文档消息传递跨文档消息传送(cross-document messaging)简称XDM。其核心方法是postMessage()方法。 postMessage()方法接受两个参数:一条消息和一个表示消息接收方来自哪个域的字符串。123// 注意:所有支持XDM的浏览器也支持iframe的`contentWindow`属性var iframeWindow = document.getElementById('myframe').contentWindow;iframeWindow.postMessage('A secret', 'https://yeasoenzhang.github.io'); 尝试向内嵌框架中发送一条消息,并指定框架中的文档必须来源于https://yeasonzhang.github.io域。 接收到XDM消息时,会触发window对象的message事件,这个事件是以异步形式触发。传递的onmessage处理程序的事件对象包含三个重要信息: data:作为postMessage()第一个参数传入的字符串数据 origin:发送消息的文档所在的域。 source:发送消息的文档的window对象的代理。 123456789EventUtil.addHandler(window, "message", function(event){ //确保发送消息的域是已知的域 if (event.origin == "https://yeasonzhang.github.io"){ //处理接收到的数据 processMessage(event.data); //可选:向来源窗口发送回执 event.source.postMessage("Received!", "http://p2p.wrox.com"); }}); XDM 还有一些怪异之处。首先,postMessage()的第一个参数最早是作为“永远都是字符串”来实现的。但后来这个参数的定义改了,改成允许传入任何数据结构。可是,并非所有浏览器都实现了这一变化。为保险起见,使用postMessage()时,最好还是只传字符串。如果你想传入结构化的数据,最佳选择是先在要传入的数据上调用JSON.stringify(),通过postMessage()传入得到的字符串,然后再在onmessage 事件处理程序中调用JSON.parse()。 原生拖放拖放事件拖动某个元素时,将依次触发的事件: dragstart drag dragend 当某个元素被拖动到一个有效的放置目标时,会依次触发下列事件: dragenter dragover dragleave(离开)或drag(放进去了) dataTransfer对象dataTransfer对象,它是事件对象的一个属性,用于被拖动元素向放置目标传递字符串格式的数据。该对象有两个主要方法: getData() setData()1234567//设置和接收文本数据event.dataTransfer.setData("text", "some text");var text = event.dataTransfer.getData("text");//设置和接收URLevent.dataTransfer.setData("URL", "http://www.wrox.com/");var url = event.dataTransfer.getData("URL"); 不过,保存在dataTransfer对象中的数据只能在drap事件处理程序中读取。如果在ondrop 处理程序中没有读到数据,那就是dataTransfer 对象已经被销毁,数据也丢失了。 drapEffect 与 effectAlloweddateTransfer对象有两个属性: dropEffect effectAllowed dropEffect,属性可以知道被拖动的元素能够执行那种放置行为。 none:不能放在这里 move:应该把拖放的元素移动到放置目标 copy:应该把拖动的元素复制到放置目标 link:表示放置目标会打开拖动的元素 要使用dropEffect属性,必须在ondragenter事件处理程序中针对放置目标来设置。 effectAllowed属性表示允许拖动元素的哪种dropEffect。 uninitialized:没有给被拖动的元素放置任何放置行为 none:被拖动的元素不能有任何行为 copy:只允许值为copy的dropEffect link:只允许值为link的dropEffect move:只允许值为move的dropEffect copyLink:允许值为copy和link的dropEffect copyMove:允许值为copy和move的dropEffect linkMove:允许值为link和move的dropEffect all: 允许任意dropEffect 必须在ondragstart 事件处理程序中设置effectAllowed 属性。 可拖动HTML5为所有元素规定了draggable属性,表示元素是否可以拖动。只有图像和链接的draggable默认是true1234<!-- 让这个图像不可以拖动 --><img src="smile.gif" draggable="false" alt="Smiley face"><!-- 让这个元素可以拖动 --><div draggable="true">...</div> 其他成员HTML5规定了dateTransfer对象还应该包含下列方法和属性。 addElement(element) clearData(format) setDragImage(element, x, y) type 错误处理与调试 错误处理try-catch 语句12345try { // 可能出错的代码} catch (err) { // 处理发生的错误} finally子句只要代码中包含finially子句,无论try还是catch语句中的return语句都将被忽略。 错误类型 Error EvalError RangeError ReferenceError SyntaxError TypeError URIError 123456789try { someFunction();} catch (error) { if (error instanceof TypeError) { //... } else { // }} 抛出错误与try-catch 语句相配的还有一个throw 操作符,用于随时抛出自定义错误。抛出错误时,必须要给throw 操作符指定一个值,这个值是什么类型,没有要求。1234throw 12345;throw "Hello world!";throw true;throw { name: "JavaScript"}; 遇到throw操作符时,代码会立即停止执行。只有当try-catch语句捕获到被抛出值,代码才会继续执行 自定义错误类型可以利用原型链通过继承Error创建自定义错误类型。需要为新创建的错误类型指定name和message属性12345678function CustomError (message) { this.name = 'CustomError'; this.message = message;}CustomError.prototype = new Error();throw new CustomError('Error msg'); Error事件任何没有通过try-catch处理的错误都会触发window对象的error事件。 在任何Web浏览器中,onerror事件处理程序都不会创建event对象,但它可以接受三个参数:错误消息、错误所在的URL和行号。 要指定onerror 事件处理程序,必须使用如下所示的DOM0 级技术,它没有遵循“DOM2 级事件”的标准格式(addEventListener)。123window.onerror = function(message, url, line){ alert(message);}; 只要发生错误,无论是不是浏览器生成的,都会触发error事件,然后让浏览器的默认机制发挥作用,这时候我们需要阻止浏览器的默认行为(return false)。1234window.onerror = function (message, url, line) { console.log(message); retrun false;} 常见的错误类型 类型转换错误 数据类型错误 通信错误 在数据检测的时候,基本类型的值应该使用typeof来检测,对象的值应该使用instanceof。 JSON 解析与序列化JSON对象JSON对象有两个方法:stringify和parse()。123456789var book = { title: "Professional JavaScript", authors: [ "Nicholas C. Zakas" ], edition: 3, year: 2011};var jsonText = JSON.stringify(book); 以上就把Javascript对象序列化为一个JSON字符串(没有空格和缩进)1{"title":"Professional JavaScript","authors":["Nicholas C. Zakas"],"edition":3,"year":2011} 如果传给JSON.parse()的字符串不是有效的JSON,会抛出错误。 序列化选项JSON.stringify()除了要序列化的JS对象外,还可以接受两个参数,一个是过滤器(数组或函数),第二个参数是一个选项,表示是都在JSON字符串中保留缩进。 过滤结果123456789var book = { "title": "Professional JavaScript", "authors": [ "Nicholas C. Zakas" ], edition: 3, year: 2011};var jsonText = JSON.stringify(book, ["title", "edition"]); 第二个参数中包含两个字符串"title", "edition",所以只会返回对应的属性1{"title":"Professional JavaScript","edition":3} 过滤器为函数1234567891011121314151617181920var book = { "title": "Professional JavaScript", "authors": [ "Nicholas C. Zakas" ], edition: 3, year: 2011};var jsonText = JSON.stringify(book, function(key, value){ switch(key){ case "authors": return value.join(",") case "year": return 5000; case "edition": return undefined; default: return value; }}); 注:返回undefined删除该属性,上例的edition属性就会被删除。 字符串缩进JSON.stringify()方法的第三个参数用于控制结果中的缩进和空白符。可以是数字,表示缩进的空格数;也可以是字符串,将该字符串作为缩进的表示。 toJSON()方法解析选项JSON.parse()方法也可以接受第二参数,该参数是一个函数(被称为还原函数),传入函数的参数均为key, value。 如果还原函数返回undefined,则表示要从结果中删除响应的键。 Ajax与Comet XMLHttpRequest 对象XHR的用法 open('method', 'url', bool):第三个参数表示是否异步发送 send():接受一个参数作为请求主体发送的数据,如果不需要则传入null XHR对象的属性 responseText:作为相应主体被返回的文本 responseXML:如果相应的内容类型是"text/xml"或"application/xml",这个属性中将包含这响应数据的XML DOM文档 status:响应的HTTP状态 statusText:HTTP状态的说明 同步请求1234567xhr.open("get", "example.txt", false);xhr.send(null);if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText);} else { alert("Request was unsuccessful: " + xhr.status);} readyState:表示请求/响应过程的阶段 0:未初始化,尚未调用open()方法 1:启动,调用了open()方法,尚未调用send()方法 2:发送,调用了send()方法,尚未接收到响应。 3:接收,接收到部分响应数据 4:完成,已经接收到全部响应数据 123456789101112var xhr = createXHR();xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } }};xhr.open("get", "example.txt", true);xhr.send(null); abort():在接收到响应之前通过该方法取消异步请求。建议调用这个方法之后,对XHR对象进行解引用操作。 HTTP 头部信息默认情况下,在发送XHR请求的同时,还会发送下列头部信息: Accept:浏览器能够处理的内容类型 Accept-Charset:浏览器能够显示的字符集 Accept-Encoding:浏览器能够处理的压缩编码 Accept-Language:浏览器当前设置的语言 Connection:浏览器与服务器之间连接的类型 Cookie: 当前页面的 Cookie Host:发出请求的页面所在的域 Referer:发出请求的页面的URI User-Agent:浏览器的用户代理 自定义请求头部信息,使用setRequestHeader()方法,该方法接受两个参数:头部字段的名称和头部字段的值。 要成功发送请求头部信息,必须在调用open()方法之后且调用send()方法之前调用serRequestHeader()。1234567var xhr = createXHR();xhr.onreadystatechange = function(){ // ...};xhr.open("get", "example.php", true);xhr.setRequestHeader("MyHeader", "MyValue");xhr.send(null); 注建议使用自定义的头部字段名称,不要使用浏览器正常发送的字段名称,否则有可能会影响服务器的响应。有的浏览器允许开发人员重写默认的头部信息,但有的浏览器则不允许这样做。 调用XHR对象的getResponseHeader()方法,接受一个参数:头部字段名称。就能取得相应的响应头部信息。调用getAllResponseHeaders()方法可以取得包含所有头部信息的字符串。 GET请求使用GET请求经常会发生一个错误,就是查询字符串的格式有问题。查询字符串中每个参数的名称和值都必须使用encodeURIComponent()进行编码,然后才能放到URL的末尾。123456789101112function addURLParam(url, name, value) { url += (url.indexOf("?") == -1 ? "?" : "&"); url += encodeURIComponent(name) + "=" + encodeURIComponent(value); return url;}var url = "example.php";//添加参数url = addURLParam(url, "name", "Nicholas");url = addURLParam(url, "book", "Professional JavaScript");//初始化请求xhr.open("get", url, false); POST请求如果我们希望用XHR模仿表单提交,需要将Content-Type头部信息设置为application/x-www-form-urlencoded(表单提交的内容类型)12345678910111213141516function submitData(){ var xhr = createXHR(); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("post", "postexample.php", true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); var form = document.getElementById("user-info"); xhr.send(serialize(form));} XMLHttpRequest 2 级FormDataFormData为序列化表单以及创建于表单格式相同的数据提供了便利。12var data = new FormData();data.append('name', 'Yeaseon'); append方法可以将表单的字段和值,传入FormData对象中。也可以预先填入表单中的字段:1var data = new FormData(document.form[0]); FormData的方便就在于不必手动修改XHR对象的请求头部。 超时设定XHR对象添加了一个timeout属性,表示请求在等待多少毫秒之后终止。如果规定时间内浏览器没有收到响应,就会触发timeout事件,进而调用ontimeout事件处理程序。12345678910var xhr = createXHR();xhr.onreadystatechange = function(){ // ...};xhr.open("get", "timeout.php", true);xhr.timeout = 1000; //将超时设置为1 秒钟xhr.ontimeout = function(){ alert("Request did not return in a second.");};xhr.send(null); 超时之后请求终止,但是此时的readyState可能已经变为了4,就意味着会调用onreadystatechange事件。 可是,如果在超时终止请求之后再访问status 属性,就会导致错误。为避免浏览器报告错误,可以将检查status 属性的语句封装在一个try-catch语句当中。 overrideMimeType()方法用于重写XHR响应的MIME类型。因为返回响应的MIME 类型决定了XHR 对象如何处理它,所以提供一种方法能够重写服务器返回的MIME 类型是很有用的。1234var xhr = createXHR();xhr.open("get", "text.php", true);xhr.overrideMimeType("text/xml");xhr.send(null); 进度事件有以下6个进度事件: loadstart:在接收到响应数据的第一个字节触发 progress:在接收响应期间持续不断地触发 error:在请求发生错误时触发 abort:在因为调用abort()方法而终止连接时触发 load:在接收到完整的响应数据时触发 loadend:在通信完成或者触发error、abort,或load事件后触发 progress事件onprogress事件处理程序会接收到一个event对象,target属性指向XHR对象,包含着三个额外的属性: lengthComputable:表示进度信息是否可用的布尔值 position:表示已经接受的字节数 totalSize:表示根据Content-Length响应头部确定的预期字节数。 跨资源共享IE对CORS的实现微软在IE8中引入了XDR类型,类似与XHR对象,两者的不同之处: cookie不会随请求发送,也不会随响应返回 只能设置请求头部信息中的Content-Type字段 不能访问响应头部信息 只支持GET和POST请求 请求返回之后,就会触发load事件,响应数据也会保存在responseText属性中:123456789var xdr = new XDomainRequest();xdr.onload = function () { console.log(xdr.responseText);};xdr.onerror = function(){ alert("An error occurred.");};xdr.open('get', 'http://..../xxx/');xdr.send(null); 在请求返回之前可以调用abort()方法终止请求。1xdr.abort(); XDR对象也支持timeout属性以及ontimeout事件处理程序12345678910111213var xdr = new XDomainRequest();xdr.onload = function(){ alert(xdr.responseText);};xdr.onerror = function(){ alert("An error occurred.");};xdr.timeout = 1000;xdr.ontimeout = function(){ alert("Request took too long.");};xdr.open("get", "http://www.somewhere-else.com/page/");xdr.send(null); 为了支持POST请求,XDR对象提供了contentType属性,用来表示发送数据的格式。12345678910var xdr = new XDomainRequest();xdr.onload = function () { //}xdr.onerror = function () { //}xdr.open('post', 'http://www.somewhere-else.com/page/');xdr.contentType = 'application/x-www-form-urlencoded';xdr.send('name1=value1&name2=value2'); 其他浏览器对CORS的实现与IE中的XDR对象不同,通过跨域XHR对象可以访问status和statusText属性,并且支持同步请求。同时也有一些限制: 不能使用setRequestHeader()设置自定义头部 不能发送和接收cookie 调用getAllResponseHeaders()方法总会返回空字符串 123456789101112var xhr = createXHR();xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } }};xhr.open("get", "http://www.somewhere-else.com/page/", true);xhr.send(null); 其他跨域技术图像Ping12345var img = new Image();img.onload = img.onerror = function(){ alert("Done!");};img.src = "http://www.example.com/test?name=Nicholas"; JSONPJSONP是JSON with padding的简写。JSONP只不过时被包含在函数调用中的JSON:1callback({"name": "Yeaseon"}); JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是请求中指定的。下面是一个经典的JSONP请求1http://freegeoip.net/json/?callback=handleResponse 这里指定的回调函数的名字叫做handleResponse。 JSONP是通过动态<script>元素来使用的,使用时可以为src属性指定一个跨域URL。 1234567function handleResponse(response){ alert("You’re at IP address " + response.ip + ", which is in " + response.city + ", " + response.region_name);}var script = document.createElement("script");script.src = "http://freegeoip.net/json/?callback=handleResponse";document.body.insertBefore(script, document.body.firstChild); 服务器发送事件SSE支持短轮询、长轮训和HTTP流,而且能在断开连接时自动确定何时重新连接。 SSE API要预订新的事件流,首先要创建一个新的EventSource对象,并传入一个入口点:1var source = new EventSource('myevents.php'); 传入的URL必须与创建对象的页面同源。EventSource的实例有一个readyState属性:0表示正连接到服务器,1表示打开了连接,2表示关闭了连接。EventSource实例还有三个事件: open:在建立连接时触发 message:在从服务器接收到新事件时触发 error:在无法建立连接时触发 服务器发回的数据以字符串形式保存在event.data中。默认情况下,EventSource对象会保持与服务器的活动连接。如果想强制立即断开连接并且不在重新连接,可以调用close()方法。 Web Sockets由于 Web Sockets 使用了自定义的协议,所以 URL 模式也略有不同。未加密的连接不再是 http:// ,而是 ws:// ;加密的连接也不是 https:// ,而是 wss:// 。 Web Sockets API创建一个WebSockets实例对象:1var socket = new WebSocket("ws://www.example.com/server.php"); WebSocket也有一个表示当前状态的readyState属性: WebSocket.OPENING (0) :正在建立连接 WebSocket.OPEN (1):已经建立连接 WebSocket.CLOSING (2):正在关闭连接 WebSocket.CLOSE (3):已经关闭连接 发送和接收数据向服务器发送数据,使用send()方法并传入任意字符串:12var socket = new WebSocket('ws:// www.example.com/server.php');socket.send('Hello World'); Web Sockets只能发送纯文本数据,对于复杂的数据结构,在发送之前,必须进行序列化。1234567var message = { time: new Date(), text: 'Hello world', clientId: 'adfalsf39'};socket.send(JSON.stringify(message)); 当服务器向客户端发来消息时,WebSocket对象就会触发message事件。这个message事件与其他传递消息的协议类似,也是把返回的数据保存在event.data属性中。1234socket.onmessage = function (event) { var data = event.data; // ....} 与send()类似,event.data中返回的数据也是字符串,需要手工解析这些数据。 其他事件WebSocket对象还有其他三个事件,在连接生命周期的不同阶段触发: open:在成功建立连接时触发 error:在发生错误时触发,连接不能持续 close:在连接关闭时触发 WebSocked对象不支持DOM 2级事件监听:12345678910var socket = new WebSocket("ws://www.example.com/server.php");socket.onopen = function(){ alert("Connection established.");};socket.onerror = function(){ alert("Connection error.");};socket.onclose = function(){ alert("Connection closed.");}; 高级技巧 高级函数安全的类型检测用于区分原生和非原生JavaScript对象,通过Object.prototype.toString()。1234567891011function isArray(value){ return Object.prototype.toString.call(value) == "[object Array]";}function isFunction(value){ return Object.prototype.toString.call(value) == "[object Function]";}function isRegExp(value){ return Object.prototype.toString.call(value) == "[object RegExp]";} 作用域安全的构造函数防止构造函数内this指针的指向被改变(指向window)123456789function Person (name, age, job) { if (this instanceof Person) { this.name = name; this.age = age; this.job = job; } else { return new Person(name, age, job); }} 惰性载入函数12345678910111213141516171819202122function createXHR(){ if (typeof XMLHttpRequest != "undefined"){ return new XMLHttpRequest(); } else if (typeof ActiveXObject != "undefined"){ if (typeof arguments.callee.activeXString != "string"){ var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"], i,len; for (i=0,len=versions.length; i < len; i++){ try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (ex){ //跳过 } } } return new ActiveXObject(arguments.callee.activeXString); } else { throw new Error("No XHR object available."); }} 第一种改法:1234567891011121314151617181920212223242526272829function createXHR () { if (typeof XMLHttpRequest != 'undefined') { createXHR = function () { return new XMLHttpRequest(); }; } else if (typeof ActiveXObjext != 'undefined') { createXHR = function () { if (typeof arguments.callee.activeXString != 'string') { var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"], i,len; for (i = 0; len = versions.length; i < len; i++) { try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (e) { // skip } } } return new ActiveXObject(arguments.callee.activeXString); }; } else { createXHR = function () { throw new Error('No XHR object available.'); } } return createXHR();} 第二种改法:12345678910111213141516171819202122232425262728var createXHR = (function () { if (typeof XMLHttpRequest != 'undefined') { return function () { return new XMLHttpRequest(); }; } else if (typeof ActiveXObjext != 'undefined') { return function () { if (typeof arguments.callee.activeXString != 'string') { var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"], i,len; for (i = 0; len = versions.length; i < len; i++) { try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (e) { // skip } } } return new ActiveXObject(arguments.callee.activeXString); }; } else { return function () { throw new Error('No XHR object available.'); } }})(); 函数绑定bind()函数,语法如下:12345function bind (fn, context) { return function () { return fn.apply(context, arguments); }} 离线应用与客户端存储 离线检测navigator.onLine属性可以判断设备否能访问网络。 HTML5定义两个事件:online和offline,当网络状态变化时,分别触发这两个事件:123456EventUtil.addHandler(window, 'online', function () { console.log('online');});EventUtil.addHandler(window, 'offline', function () { console.log('offline');}); 数据存储Web存储机制Web Storage规范包含两种对象的定义:sessionStorage和globalStorage。这两个对象在支持的浏览器中都是以windows对象属性的形式存在。 Storage类型Storage类型提供最大的存储空间来存储名值对。 clear():删除所有值 getItem(name):根据指定的名字name获取对应的值 key(index):获得index位置处的值的名字 removeItem(name):删除由name指定的名值对 setItem(name, value):为指定的name设置一个对应的值 sessionStorage对象sessionStorage对象存储特定于某个会话的数据,也就是该数据只保持到浏览器关闭。存储在sessionStorage中的数据可以跨越页面刷新而存在。1234567891011121314//使用方法存储数据sessionStorage.setItem("name", "Nicholas");//使用属性存储数据sessionStorage.book = "Professional JavaScript";//使用方法读取数据var name = sessionStorage.getItem("name");//使用属性读取数据var book = sessionStorage.book;//使用delete 删除一个值——在WebKit 中无效delete sessionStorage.name;//使用方法删除一个值sessionStorage.removeItem("book"); 可以通过结合length属性和key()方法来迭代sessionStorage中的值:12345for (var i = 0, len = sessionStorage.length; i < len; i++) { var key = sessionStorage.key(i); var value = sessionStorage.getItem(key); console.log(key + ' = ' + value);} 还可以使用for-in循环来迭代sessionStorage中的值:1234for (var key in sessionStorage) { var value = sessionStorage.getItem(key); console.log(key + ' = ' + value);} globalStorage对象这个对象的目的是跨越会话存储数据,,但有特定的访问限制。要使用globalStorage,首先要指定哪些域可以访问该数据。12345// 保存数据globalStorage['wrox.com'].name = 'Yeaseon';// 获取数据var name = globalStorage['wrox.com'].name; 上例,访问的是针对域名wrox.com的存储空间。globalStorage对象不是Storage的实例,而具体的globalStorage['wrox.com']才是。这个存储空间对于wrox.com及其所有子域都是可以访问的。1234globalStorage["www.wrox.com"].name = "Yeaseon";globalStorage["www.wrox.com"].book = "Professional JavaScript";globalStorage["www.wrox.com"].removeItem("name");var book = globalStorage["www.wrox.com"].getItem("book"); localStorage对象localStorage对象是HTML5规范中作为持久保存客户端数据的方案,并且取代globalStorage。要访问同一个localStorage对象,页面必须来自同一个域名(子域名无效),必须同源。123456789//使用方法存储数据localStorage.setItem("name", "Nicholas");//使用属性存储数据localStorage.book = "Professional JavaScript";//使用方法读取数据var name = localStorage.getItem("name");//使用属性读取数据var book = localStorage.book; storage事件对Storage对象进行任何修改,都会在文档上触发storage事件。这个事件的event对象有以下属性。 domain:发生变化的存储空间的域名 key:设置或删除的键名 newValue:如果是设置值,则是新值;如果是删除键,则是null oldValue:键被更改之前的值 在这四个属性中,IE8 和Firefox 只实现了domain 属性。在撰写本书的时候,WebKit 尚不支持storage 事件 IndexedDBIndexed Database API,简称IndexedDB,是在浏览器中保存结构化数据的一种数据库。IndexedDB设计的操作完全是异步进行。1var indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB; 数据库IndexedDB就是一个数据库,它最大的特色就是使用对象保存数据,而不是使用表来保存数据。 indexDB.open(),传入一个数据库参数。如果该数据库存在就会发送一个打开它的请求;如果该数据库不存在,就会发送一个创建并打开它的请求。请求会返回一个IDBRequest对象,这个对象上可以添加onerror和onsuccess事件处理程序。123456789var request, database;request = indexedDB.open('admin');request.onerror = function (event) { console.log(event.target.errorCode);};request.onsuccess = function (event) { database = event.target.result;}; event.target都指向request对象,因此他们可以互换使用。发生错误了,event.target.errorCode中将会保存一个错误码: IDBDatebaseException.UNKNOWN_ERR(1):意外错误 IDBDatebaseException.NON_TRANSIENT_ERR(2):操作不合法 IDBDatebaseException.NOT_FOUND_ERR(3):未发现要操作的数据库 IDBDatebaseException.CONSTRAINT_ERR(4):违反了数据库约束 IDBDatebaseException.DATA_ERR(5):提供给事务的数据不能满足要求 IDBDatebaseException.NOT_ALLOWED_ERR(6):操作不合法 IDBDatebaseException.TRANSACTION_INACTIVE_ERR(7):试图重用已完成的事务 IDBDatebaseException.ABORT_ERR(8):请求中断 IDBDatebaseException.READ_ONLY_ERR(9):试图在只读模式下写入或修改数据 IDBDatebaseException.TIMEOUT_ERR(10):在有效时间内未完成操作 IDBDatebaseException.QUOTA_ERR(11):磁盘空间不足 指定数据库版本号,通过setVersion()方法:1234567891011if (database.version != '1.0') { request = database.setVersion('1.0'); request.onerror = function (event) { console.log(event.target.errorCode); }; request.onsuccess = function (event) { console.log(''Database name: ' + database.name + ', Version: ' + database.version); }} else { console.log(''Database name: ' + database.name + ', Version: ' + database.version);} 对象存储空间假设要保存的用户记录由用户名、密码等组成,那么保存一条记录的对象应该类似:123456var user = { username: '007', firstname: 'James', lastname: 'Bond', password: 'foo'} 如果使用username属性作为这个对象存储空间的键,这个username必须全局唯一,而且大部分时候都要通过这个键来访问数据。1var store = db.createObjectStore('users', { keyPath: 'username' }); 其中第二个参数中的keyPath属性,就是空间中将要保存的对象的一个属性,而这个属性将作为存储空间的键来使用。 通过add()或put()方法来向存储空间添加数据。着两个方法都接收一个参数,就是要保存的对象。123456789101112131415//users 中保存着一批用户对象var i=0, request, requests = [], len = users.length;while(i < len){ request = store.add(users[i++]); request.onerror = function(){ //处理错误 }; request.onsuccess = function(){ //处理成功 }; requests.push(request);} 事务在数据库对象上调用transaction()可以创建事务,任何时候,只要想读取或修改数据,都要通过事务来组织所有操作。12// 创建事务var transaction = db.transaction(); 可以传入要访问的一或多个对象存储空间。123var transaction = db.transaction('users');var transaction = db.transaction(['users', 'anotherStore']); 前面这些事务都是以只读方式访问数据。要修改访问方式,必须在创建事务时传入第二个参数,这个参数表示访问模式: IDBTransaction.READ_ONLY(0):只读 IDBTransaction.READ_WRITE(1):读写 IDBTransaction.VERSION_CHANGE(2):改变 在Chrome中叫做webkitIDBTransaction,可以使用一下代码兼容:1var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction; 这样就能方便的指定transaction()第二个参数:1var transaction = db.transaction('users', IDBTransaction.READ_WRITE); 取得事务的索引后,使用objectStore()方法并传入存储空间的名称,就可以访问指定的存储空间。然后通过如下方法操作对象: add() put() get() delete() clear() 12345678var request = db.transaction('users').objectStore('users').get('007');request.onerror = function (event) { console.log('Did not get the object!');};request.onsuccess = function (event) { var result = event.target.result; console.log(result.firstName); // 'James'}; 也可以针对事务对象本身进行事件处理,存在两个事件onerror,oncomplete:123456transaction.onerror = function (event) { // 整个事务都被取消了}transaction.oncomplete = function (event) { // 整个事务都成功完成了} 注:在oncomplete事件的事件对象中访问不到get()请求返回的数据,必须在onsuccess事件中处理。 键范围1var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange; 有四种定义键范围的方法: only():取得指定对象的键 lowerBound():指定结果集的下界 upperBound():指定结果集的上界 bound():同时指定上、下界 1234567891011121314151617181920var onlyRange = IDBKeyRange.only("007");//从键为"007"的对象开始,然后可以移动到最后var lowerRange = IDBKeyRange.lowerBound("007");//从键为"007"的对象的下一个对象开始,然后可以移动到最后var lowerRange = IDBKeyRange.lowerBound("007", true);//从头开始,到键为"ace"的对象为止var upperRange = IDBKeyRange.upperBound("ace");//从头开始,到键为"ace"的对象的上一个对象为止var upperRange = IDBKeyRange.upperBound("ace", true);//从键为"007"的对象开始,到键为"ace"的对象为止var boundRange = IDBKeyRange.bound("007", "ace");//从键为"007"的对象的下一个对象开始,到键为"ace"的对象为止var boundRange = IDBKeyRange.bound("007", "ace", true);//从键为"007"的对象的下一个对象开始,到键为"ace"的对象的上一个对象为止var boundRange = IDBKeyRange.bound("007", "ace", true, true);//从键为"007"的对象开始,到键为"ace"的对象的上一个对象为止var boundRange = IDBKeyRange.bound("007", "ace", false, true); 新型的API Page Visibility APIPage Visibility API 是为了让开发人员知道页面是否对用户可见推出的。 document.hidden:表示页面是否隐藏的布尔值。 document.visibilityState 页面在后台标签页中或浏览器最小化 页面在前台标签页中 实际的页面已经隐藏,但用户可以看到页面的预览 页面在屏幕外执行预渲染处理 visibilitychange事件:当文档可见性发生改变时,触发该事件。 Geolocation APIGeolocation API 在浏览器中的实现是navigator.geolocation对象。 getCurrentPosition() 调用这个方法就会触发请求用户共享地理定位信息的对话框。这个方法接收三个参数:成功回调,可选的失败回调和可选的选项对象。 成功回调会接收到一个Position对象参数,该对象有两个属性:coords和timestamp。 coords对象中包含于位置相关的信息: latitude:十进制度数表示的纬度 longitude:十进制度数表示的经度 accuracy:经纬度坐标的精度,以米为单位 123navigator.geolocation.getCurrentPosition(function (position) { drawMapCenteredAt(position.coords.latitude, position.coords.longitude);}); 失败回调在被调用的时候也会接受到一个参数,这个参数是一个对象,包含连个属性:message和code。code保存着一个数值,表示错误的类型:用户拒绝共享(1)、位置无效(2)或者超时(3)。123456navigator.geolocation.getCurrentPosition(function (position) { drawMapCenteredAt(position.coords.latitude, position.coords.longitude);}, function (error) { console.log('Error code:' + error.code); console.log('Error message:' + error.message);}); 第三个参数是一个可选对象,用于设定信息的类型。可以设置的选项有三个: enableHightAccuracy:布尔值,表示必须尽可能使用最准确定的位置信息 timeout:以毫秒数表示的等待位置信息的最长时间 maximumAge:表示上一次取得的坐标信息的有效时间,以毫秒表示,如果时间到则重新取得新坐标信息 12345678910navigator.geolocation.getCurrentPosition(function (position) { drawMapCenteredAt(position.coords.latitude, position.coords.longitude);}, function (error) { console.log('Error code:' + error.code); console.log('Error message:' + error.message);}, { enableHighAccuracy: true, timeout: 5000, maximumAge: 25000}); File APIFile API 在表单中的文件输入字段的基础上,又添加了一些直接访问文件信息的接口。HTML5在DOM中为文件输入元素添加了一个files集合。每个File对象都有下列只读属性。 name:本地文件系统的文件名 size:文件的字节大小 type:字符串,文件的MIME类型 lastModifiedDate:字符串,文件上一次修改的时间 FileReader 类型FileReader 类型实现的是一种异步文件读取机制。可以把FileReader想象成XMLHttpRequest。 readAsText(file, encoding):以纯文本形式读取文件,将读取到的文本保存在result属性中,第二个参数用于指定编码类型(可选) readAsDataURL(file):读取文件并将文件以数据URI形式保存在result属性中 readAsBinaryString(file):读取文件并将一个字符串保存在result属性中,字符串中的每个字符表示一字节 readAsArrayBuffer(file):读取文件并将一个包含文件内容的ArrayBuffer保存在result属性中 由于读取过程是异步的,所以FileReader提供了三个事件: progress error load progress事件,每50ms就会触发一次,通过事件对象可以获得与XHR的progress事件相同的信息: lengthComputable loaded total 由于种种原因无法读取文件,都会触发error事件,相关信息都会保存到FileReader的error属性中。error.code即错误码: 1:为找到文件 2:安全性错误 3:读取中断 4:文件不可读 5:编码错误 文件加载成功后会触发load事件。 12345678910111213141516171819202122232425262728293031323334353637383940var filesList = document.getElementById('files-list');EventUtil.addHandler(filesList, 'change', function (event) { var info = '', output = document.getElementById('output'), progress = document.getElementById('progress'), files = EventUtil.getTarget(event).files, type = 'default', reader = new FileReader(); if (/image/.test(files[0].type)) { reader.readAsDateURL(files[0]); type = 'image'; } else { reader.readAsText(files[0]); type = 'text'; } reader.onerror = function () { output.innerHTML = 'Could not read file, error code is ' + reader.error.code; }; reader.onprogress = function () { if (event.lengthComputable) { progress.innerHTML = event.loaded + '/' + event.total; } }; reader.onload = function () { var html = ''; switch (type) { case 'image': html = '<img src=\"' + reader.result + '\">'; break; case 'text': html = reader.result; break; } output.innerHTML = html; };}); 读取拖放的文件从桌面上把文件拖放到浏览器中会触发drop 事件。而且可以在event.dataTransfer. files中读取到被放置的文件,当然此时它是一个File 对象,与通过文件输入字段取得的File 对象一样。1234567891011121314151617181920212223var droptarget = document.getElementById('droptarget');function handleEvent(event) { var info = '', output = document.getElementById('output'); files, i, len; EventUtil.preventDefault(event); if (event.type == 'drop') { files = event.dataTransfer.files; //转换成File对象 i = 0; len = files.length; while (i < len) { info += files[i].name + ' (' + files[i].type + ', ' + files[i].size + ' bytes)<br>'; i++; } output.innerHTML = info; }}// 阻止默认事件,只有 drop 事件会被处理EventUtil.addHandler(droptarget, "dragenter", handleEvent);EventUtil.addHandler(droptarget, "dragover", handleEvent);EventUtil.addHandler(droptarget, "drop", handleEvent); 使用XHR上传文件创建一个FormDate对象,通过它调用append()方法并传入相应的File对象作为参数,再把FormData对象传递给XHR的send()方法。12345678910111213141516171819202122232425262728293031323334var droptarget = document.getElementById('droptarget');function handleEvent(event) { var info = '', output = document.getElementById('output'), data, xhr, files, i, len; EventUtil.preventDefault(event); if (event.type == 'drop') { data = new FormData(); files = event.dataTransfer.files; i = 0; len = files.length; while (i < len) { data.append('file' + i, files[i]); i++; } xhr = new XMLHttpRequest(); xhr.open('post', 'FileAPIExapleUpload.php', true); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { console.log(xhr.responseText); } }; xhr.send(data); }}EventUtil.addHandler(droptarget, "dragenter", handleEvent);EventUtil.addHandler(droptarget, "dragover", handleEvent);EventUtil.addHandler(droptarget, "drop", handleEvent);]]></content>
<tags>
<tag>JavaScript 笔记</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Vuex 通俗版教程]]></title>
<url>%2F2017%2F03%2F16%2FVuex-%E9%80%9A%E4%BF%97%E7%89%88%2F</url>
<content type="text"><![CDATA[本文基本上是官方教程的盗版,用通俗易懂的文字讲解Vuex,也对原文内容有删减。 如果你对以上声明不介意,那么就可以继续看本文,希望对你有所帮助。 学习一个新技术,必须要清楚两个W,”What && Why”。 “XX 是什么?”,”为什么要使用 XX ,或者说 XX 有什么好处”,最后才是”XX 怎么使用”。 Vuex是什么?Vuex 类似 Redux 的状态管理器,用来管理Vue的所有组件状态。 为什么使用Vuex?当你打算开发大型单页应用(SPA),会出现多个视图组件依赖同一个状态,来自不同视图的行为需要变更同一个状态。 遇到以上情况时候,你就应该考虑使用Vuex了,它能把组件的共享状态抽取出来,当做一个全局单例模式进行管理。这样不管你在何处改变状态,都会通知使用该状态的组件做出相应修改。 下面讲解如何使用Vuex。 最简单的Vuex示例本文就不涉及如何安装Vuex,直接通过代码讲解。 123456789101112131415import Vue from 'vue';import Vuex form 'vuex';Vue.use(Vuex);const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }}) 以上就是一个最简单的Vuex,每一个Vuex应用就是一个store,在store中包含组件中的共享状态state和改变状态的方法(暂且称作方法)mutations。 需要注意的是只能通过mutations改变store的state的状态,不能通过store.state.count = 5;直接更改(其实可以更改,不建议这么做,不通过mutations改变state,状态不会被同步)。使用store.commit方法触发mutations改变state:123store.commit('increment');console.log(store.state.count) // 1 一个简简单单的Vuex应用就实现了。 在Vue组件使用Vuex如果希望Vuex状态更新,相应的Vue组件也得到更新,最简单的方法就是在Vue的computed(计算属性)获取state 123456789// Counter 组件const Counter = { template: `<div>{{ count }}</div>`, computed: { count () { return store.state.count; } }} 上面的例子是直接操作全局状态store.state.count,那么每个使用该Vuex的组件都要引入。为了解决这个,Vuex通过store选项,提供了一种机制将状态从根组件注入到每一个子组件中。 1234567891011121314151617// 根组件import Vue from 'vue';import Vuex form 'vuex';Vue.use(Vuex);const app = new Vue({ el: '#app', store, components: { Counter }, template: ` <div class="app"> <counter></counter> </div> `}) 通过这种注入机制,就能在子组件Counter通过this.$store访问:123456789// Counter 组件const Counter = { template: `<div>{{ count }}</div>`, computed: { count () { return this.$store.state.count } }} mapState函数12345computed: { count () { return this.$store.state.count }} 这样通过count计算属性获取同名state.count属性,是不是显得太重复了,我们可以使用mapState函数简化这个过程。12345678import { mapState } from 'vuex';export default { computed: mapState ({ count: state => state.count, countAlias: 'count', // 别名 `count` 等价于 state => state.count })} 还有更简单的使用方法:1234computed: mapState([ // 映射 this.count 为 store.state.count 'count']) Getters对象如果我们需要对state对象进行做处理计算,如下:12345computed: { doneTodosCount () { return this.$store.state.todos.filter(todo => todo.done).length }} 如果多个组件都要进行这样的处理,那么就要在多个组件中复制该函数。这样是很没有效率的事情,当这个处理过程更改了,还有在多个组件中进行同样的更改,这就更加不易于维护。 Vuex中getters对象,可以方便我们在store中做集中的处理。Getters接受state作为第一个参数: 12345678910111213const store = new Vuex.Store({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } }}) 在Vue中通过store.getters对象调用。12345computed: { doneTodos () { return this.$store.getters.doneTodos }} Getter也可以接受其他getters作为第二个参数:12345678getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) }, doneTodosCount: (state, getters) => { return getters.doneTodos.length }} mapGetters辅助函数与mapState类似,都能达到简化代码的效果。mapGetters辅助函数仅仅是将store中的getters映射到局部计算属性:12345678910111213import { mapGetters } from 'vuex'export default { // ... computed: { // 使用对象展开运算符将 getters 混入 computed 对象中 ...mapGetters([ 'doneTodosCount', 'anotherGetter', // ... ]) }} 上面也可以写作: 12345computed: mapGetters([ 'doneTodosCount', 'anotherGetter', // ... ]) 所以在Vue的computed计算属性中会存在两种辅助函数:123456789import { mapState, mapGetters } form 'vuex';export default { // ... computed: { mapState({ ... }), mapGetter({ ... }) }} Mutations之前也说过了,更改Vuex的store中的状态的唯一方法就是mutations。 每一个mutation都有一个事件类型type和一个回调函数handler。 1234567891011const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { // 变更状态 state.count++ } }}) 调用mutation,需要通过store.commit方法调用mutation type:1store.commit('increment') Payload 提交载荷也可以向store.commit传入第二参数,也就是mutation的payload: 1234567mutaion: { increment (state, n) { state.count += n; }}store.commit('increment', 10); 单单传入一个n,可能并不能满足我们的业务需要,这时候我们可以选择传入一个payload对象:1234567891011mutation: { increment (state, payload) { state.totalPrice += payload.price + payload.count; }}store.commit({ type: 'increment', price: 10, count: 8}) mapMutations函数不例外,mutations也有映射函数mapMutations,帮助我们简化代码,使用mapMutations辅助函数将组件中的methods映射为store.commit调用。 12345678910111213import { mapMutations } from 'vuex'export default { // ... methods: { ...mapMutations([ 'increment' // 映射 this.increment() 为 this.$store.commit('increment') ]), ...mapMutations({ add: 'increment' // 映射 this.add() 为 this.$store.commit('increment') }) }} 注 Mutations必须是同步函数。 如果我们需要异步操作,Mutations就不能满足我们需求了,这时候我们就需要Actions了。 Aciton相信看完之前的Vuex的内容,你就已经入门了。那么Action就自己进行学习吧(Action有点复杂,我还需要时间消化)。 结语上个月看Vuex还是一头雾水,现在看来Vuex也是很容易理解的。 学习一门新技术最重要的就是实践,单单看教程和demo是远远不够的。 前端路途漫漫,共勉。]]></content>
<tags>
<tag>Vue Vuex</tag>
</tags>
</entry>
<entry>
<title><![CDATA[微信浏览器Bug]]></title>
<url>%2F2017%2F03%2F06%2F%E5%BE%AE%E4%BF%A1%E6%B5%8F%E8%A7%88%E5%99%A8Bug%2F</url>
<content type="text"><![CDATA[不是一个笑话,微信浏览器真的有Bug。 一个我觉得不应该出Bug的地方,它居然有Bug,真的很坑。 问题描述在微信浏览器中做上传文件的功能,发现iOS微信浏览器正常工作,但是Android微信浏览器input[file]不能触发change事件。 思考出于“自省”,我觉得是我代码写的有问题,我是使用Vue2调用的change事件。 1<input type="file" accept="*/*" name="file" @change="fileChange()"/> 第一反应就是,微信内置浏览器不支持Vue2的@change事件。 然后我就通过原生JS的写法监听change事件: 1document.getElementsByName('file')[0].addEventListener('change', fileChange); 这样修改之后还是不能触发change事件,又改写了另一种原生写法: 1<input type="file" accept="*/*" name="file" onchange="fileChange()"/> 不出所料,这样写还是没什么效果。 之后就是疯狂的Google,发现也有人遇到类似的问题,但是没有给出实质性的解决方案。 转机发现知乎上有关于微信内置的浏览器如何上传文件?的讨论。 也在其中找到了根本的解决原因。 这位兄台说,accept="*/*"上传图片,在6.3.30版本不能触发change事件了,证明之前是可行。 微信浏览器团队在升级中引入了Bug,从2016-11-14至今也没修复。 然后我就将我的accept更改为accept="image/*"果然就可行,鉴定是Bug无误。 但是我不仅要上传图片,任意格式都要支持啊,我果断把accept属性去掉了,测试居然所有类型都能支持上传了。 当初为什么要手贱写上accept属性,不然就不会搞到崩溃。 可是,仔细想想如果没有遇到这个Bug,也不会了解到微信内置浏览器(X5内核)也会出现这种想不到的Bug。 结语谁的程序都会出错,遇到问题就应该一个个的排除,总会找到解决问题的办法。]]></content>
<tags>
<tag>微信 input change</tag>
</tags>
</entry>
<entry>
<title><![CDATA[History API 使用指北]]></title>
<url>%2F2017%2F03%2F03%2FHistory-API-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8C%97%2F</url>
<content type="text"><![CDATA[HTML5 History API,基本上是为了SPA(单页应用)而生。 History API能够在不刷新页面的情况下,通过和url匹配历史堆栈中的数据取出来,这样就能大大减少数据请求,提高用户体验。 其实说实话,能不能提高用户体验,我不知道;在历史记录中切换(前进、后退)真的是如丝般顺滑。 History API 简介在浏览器环境下,我们可以通过window.history访问我们的浏览器访问历史。可能说的有点问题,你只能得到三个值length,scrollRestoration和state。其中只有state的值是我们需要的,接下来会提到。 back()back()方法相当于点击浏览器的后退按钮。 1window.history.back(); forward()forward()方法相当于点击浏览器的前进按钮。 1window.history.forward(); go(n)go(n)方法允许你在历史session前进或者后退n次。 12345// Go back two entries.window.history.go(-2);// Go forward 3 entries.window.history.go(3); length就是我们上文在浏览器得到的length属性。 下面就是我们的重头戏,HTML5 History API,上面的属性方法只是简单的热身,你可能不需要热身。 HTML5 History APIHTML5 History API包含两个方法和一个事件。 pushState() replaceState() popstate 在详细介绍之前,我们先来看看各个浏览器对于HTML5 History API支持情况。 可以说现在主流的浏览器对于HTML5 History API支持都是很好的,不过也要考虑向下兼容。 12345if (!!history.pushState) { // support} else { // dont support} pushState()1pushState(state, title, url) state: 传递给history.state title: 似乎浏览器还没有很好支持,传null就好 url: 可选,这个参数会改变你的浏览器url 这个方法是在浏览器堆栈历史中push一条新的数据,然后将指针指向这条数据。 replaceState()1replaceState(state, title, url) replace()方法与pushState()方法类似,主要是能够替换更新pushState()的state数据。 参数描述详见pushState()的描述。 popstate事件当用户点击浏览器的前进/后退按钮,popstate事件会被调用。12345678window.addEventListener('popstate', function (event) { // update the page content});// or window.onpopstate = function (event) { // update the page content} 以上就把History API简略介绍了一下,详细说明请访问MDN。 手把手实战需求分析网上也不乏介绍H5 History API应用的文章,大概也都是比较简单的Demo。 点击链接first、second、third..,url会改变为http://html5demos.com/history#fitst,http://html5demos.com/history#second… 获取不同#first,#second的数据,渲染页面中的数据。具体实现源码。 本文,我想介绍的不是这种demo,而是比较实际的项目开发。 实现一个文件系统的浏览页面,通过url哈希值#...记录文件夹的路径。 例如,www.demo.com/filesystem.html#abc/就是根目录下abc文件夹;www.demo.com/filesystem.html#abc/def/代表abc文件夹下的def文件夹。 如上图,该目录下有文件和文件夹,点击文件会直接在浏览器打开该文件或者下载,点击文件夹会进入该文件夹,页面显示该文件夹的内容。 这样就需要我们把url的hash值动态改变,然后根据hash值确定所在文件路径向后台获取资源。 前后端接口API显示目录请求:1GET /api/v1/fileproxy/pub/abc/ HTTP/1.1 响应:1234567891011121314151617181920HTTP/1.1 200 OKContent-Type: application/json; charset=utf-8{ "error": 0, "errormsg": "success", "data": [ { "name": "a.txt", "size": 1234, "time": 1235153531, "isdir": false, }, { "name": "a.txt", "size": 1234, "time": 1235153531, "isdir": false, } ]} 打开/下载文件请求:1GET /api/v1/fileproxy/pub/abc/a.txt HTTP/1.1 从后端获取到的文件信息包括name文件(夹)名,size大小,time修改时间,isdir是否是文件夹。 流程 initGetfile() openDir(str) window.onpopstate initGetfile()函数,首次进入或者刷新页面自动执行该函数,渲染文件列表。 openDir(str)函数,当点击文件夹时调用该函数,获取新的文件列表。 window.onpopstate也就是popstate事件。 在没有使用History API时,是通过hashchange事件来触发文件列表的更新,这就导致每一次前进后退都会发起一次ajax请求,没有把之前请求过的数据进行缓存,影响用户体验。 对了在此要声明一下,psuhState()和replaceState()不会触发hashchange事件 怎么理解呢,就是当你通过psuhState()和replaceState()的第三个参数url对于hash值有更改的时候,也不会触发hashchange事件。 Chrome 和 Safari浏览器在重载页面的时候会触发popstate事件,Firefox浏览器不会。 重头戏下面将会贴上我的代码,使用Vue2 和 axios实现。 HTML123456789101112131415161718192021222324252627282930<div class="file-list"> <div class="weui-cells"> <a v-if="fileList.length" v-for="item in fileList" href="javascript:;" class="weui-cell weui-cell_access"> <!-- Dir --> <div v-if="item.isdir"> <div class="weui-cell__hd"> <span class="icon-size an-folder"></span> </div> <div class="weui-cell__bd" @click="openDir(item)"> <p>{{ item.name }}</p> </div> </div> <!-- File --> <div v-else> <div v-else class="weui-cell__hd"> <span class="icon-size an-file"></span> </div> <div class="weui-cell__bd" @click="openFile(item)"> <p>{{ item.name }}</p> </div> </div> </a> <a v-if="!fileList.length" class="weui-cell weui-cell_access" href="javascript:;"> <div class="weui-cell__bd"> <p>No such file or directory</p> </div> </a> </div></div> 将文件夹和文件分成两类进行渲染,绑定不同的点击函数,并传递参数为文件(夹)名,前面的前后端API也提示我们是通过name进行请求。 JS12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788(function (exports) { exports.app = new Vue({ el: '.file-list', data: { haveFile: true, fileList: [], }, mounted: fucntion () { // 挂载之后,自动执行 this.$nextTick(function () { this.initGetfile(); }) }, methods: { initGetfile: function () { var _this = this; var hash = _this.getHash(); var url = '/api/v1/fileproxy/' + hash; axios.get(url) .then(function (res){ _this.fileList = res.data.data; // replaceState() history.replaceState(_this.fileList, null); if (!res.data.data.length) { _this.haveFile = false; } else { _this.haveFile = true; } }) }, openDir: function (item) { var _this = this; _this.loadFile = true; var hash = _this.getHash(); var url = '/api/v1/fileproxy/-/' + hash + item.name +'/'; axios.get(url) .then(function (res) { _this.fileList = res.data.data; _this.loadFile = false; var newUrl = window.location.href + item.name + '/'; // pushState() history.pushState(_this.fileList,null, newUrl); if (!res.data.data.length) { _this.haveFile = false; } else { _this.haveFile = true; } }); }, openFile: function (item) { var _this = this; var hash = _this.getHash(); var url = '/api/v1/fileproxy/-/' + hash + item.name; window.open(url); }, getHash: function () { var hash = window.location.hash.substr(1); if (hash.substr(-1) != '/') { hash += '/'; } return hash; }, } }); window.onpopstate = function (e) { console.log('pop state'); console.log('state', JSON.stringify(e.state)); if (e.state != null) { app.fileList = e.state; } else { window.location.reload(); } }})(window); 以上就是我完成基本功能的源码,添加History API到项目中并没有做太多改动: 增加pushState()和repalceState()函数 替换原有的hashchange事件为popstate事件。 结语本没有对源码做过多的分析,因为可能需求不一样采取的处理方法也不一样,我只是提供一个#abc/def...这种hash值比较复杂需求的一种实现。 希望能够对你有所启发,也是现学现卖,如有不妥之处,望指正。]]></content>
<tags>
<tag>HTML5 History</tag>
</tags>
</entry>
<entry>
<title><![CDATA[浅谈JavaScript--声明提升]]></title>
<url>%2F2017%2F02%2F28%2F%E6%B5%85%E8%B0%88JavaScript-%E5%A3%B0%E6%98%8E%E6%8F%90%E5%8D%87%2F</url>
<content type="text"><![CDATA[JavaScript是一种动态语言,不同于C、Java等静态语言先编译后执行, 所以代码中的执行顺序并不像你看到的那样执行,有个词你需要知道声明提升, 下面我们就来聊聊声明提升。 抛砖123a = 1;var a;console.log(a); // 1 结果是1,不是undefined。虽然在var a;在a = 1;后面,但是存在声明提升,等价于:123var a;a = 1;console.log(a); 再抛一块12console.log(a); // undefinedvar a = 1; 为什么这次结果就是undefined了,声明同样提升了,但是。。。等下再告诉你,上面的代码等价于:123var a;console.log(a); // undefineda = 2; 也就是说声明虽然提升了,但是赋值操作(执行)被留在了本身的位置。 原理引擎在对解释js的代码的时候,首先进行的是编译。找到所有的声明,并用合适的作用域把它们关联起来。 so,var a = 2,js会将其看成两个声明var a; a = 2; 第三块砖123456foo();function foo() { console.log(a); // undefined var a = 2;} 这个就很好理解了吧,那么你回答下一个123456foo();var foo = function bar() { console.log(a); var a = 2;} 先别说你的答案,估计你也猜错了,不是undefined也不是2; 因为还没执行到bar(),foo()就已经报错了TypeError,函数声明可以提升,但是函数表达式的声明不能提升。 即使是具名函数表达式,在名称标识符在赋值之前也无法在作用域中使用:1234567foo(); // TypeError;bar(); // ReferenceError;var foo = function bar() { console.log(a); var a = 2;} 其实经过提升之后,代码变成了:1234567891011var foo;foo(); // TypeError;bar(); // ReferenceError;foo = function () { var bar = function () { console.log(a); var a = 2; }} 优先原则1234567891011foo(); // 1var foo;function foo () { console.log(1);}foo = function () { console.log(2);} 输出的是1,而不是2。12var foo;function foo () {...} 这两个都是声明,但是在声明中函数会首先被提升(var foo同时被忽略了)。也就变成了:12345678function foo() { console.log(1);}foo(); // 1foo = function () { console.log(2);} 注: 重复声明,后面函数声明会覆盖前面的。12345678910111213foo(); // 3function foo() { console.log(1);}foo = function () { console.log(2);}function foo() { console.log(3);} 总结为了避免踩坑,我们要做到先声明,避免重复声明。 参考:《你不知道的JavaScript》]]></content>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[移动端文件上传美化&一键上传]]></title>
<url>%2F2017%2F02%2F27%2F%E7%A7%BB%E5%8A%A8%E7%AB%AF%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E7%BE%8E%E5%8C%96-%E4%B8%80%E9%94%AE%E4%B8%8A%E4%BC%A0%2F</url>
<content type="text"><![CDATA[最近项目中文件系统需要适配移动端,发现很多PC端的交互在移动端显得很累赘。 下面就改造一下文件上传,让它变得更加适合移动端。 PC端实现方案就拿上传文件来说,之前在移动端是点击上传icon,弹出一个对话框选择文件,然后通过对话框的按钮进行上传。 分解起来就是,选择文件和上传文件,其实是两步。但是在移动端,我们点击input[file]会调用原生的文件系统(iOS是选择照片/拍照上传),百度云的做法是在调用原生的文件系统,选择完要上传的文件,无需用户点击上传按钮直接上传。现在我们来学习下百度云的做法。 美化input控件在没有使用插件的原生input[file]是很难看的,上面的PC端上传文件是使用了file-input组件,所以我们自定义样式之后会显得比较好看。 但是在移动端我们不使用弹框效果了,所以input[file]也就没必要美化,直接隐藏就好。通过上传按钮调用input[file]123456789// HTML<span class="upload" onclick="chooseFile()"></span><input type="file" accept="*/*" name="uploadFile" style="display: none; opacity: 0"/>// JSfunction chooseFile() { var fileObj = document.getElementsByName('uploadFile')[0]; fileObj.click();} 在上面的代码中,我们使用一个上传icon触发input[file]这样文件就选择完毕了 一键上传现在文件选择完成了,我们发现没有办法上传文件了,因为input[file]已经被我们隐藏了。 让我们来转换一下思维方式,为什么还需要第二步上传操作,我们不能选择完成之后直接上传吗?事实证明百度云就是这么做的。 input[file]也是一个普通的input,那我们就能使用它的change事件,这样就把思路捋顺了。 1234567891011121314151617181920212223// HTML<span class="upload" onclick="chooseFile()"></span><input type="file" accept="*/*" name="uploadFile" onchange="uploadFile()" style="display: none; opacity: 0"/>// JSfunction chooseFile() { var fileObj = document.getElementsByName('uploadFile')[0]; fileObj.click();}function uploadFile() { var formData = new formData(); var fileObj = document.getElementsByName('uploadFile')[0].files[0]; formData.appenf('file', fileObj); axios.post(url, formData) .then(function (res) { // success }) .catch(function () { // error })} 为input[file]添加一个change事件,就能在我们选择完文件,自动调用相关函数进行上传。在这里我没有使用原生的AJAX,我使用的是axios,感兴趣的话可以自行Google。 结语至此,我们就实现了一个简单移动端的上传文件,还有很多不足,会在后续工作中优化。]]></content>
<tags>
<tag>H5</tag>
</tags>
</entry>
<entry>
<title><![CDATA[提高你开发效率的工具 -- BrowserSync]]></title>
<url>%2F2017%2F01%2F23%2F%E6%8F%90%E9%AB%98%E4%BD%A0%E5%BC%80%E5%8F%91%E6%95%88%E7%8E%87%E7%9A%84%E5%B7%A5%E5%85%B7-BrowserSync%2F</url>
<content type="text"><![CDATA[Browser-Sync是一款前端实时可视化开发工具,之前老旧的开发环境需要修改完code,在浏览器按F5查看更新后的效果,这样一次不会有什么感觉繁琐,但是长时间的手动刷新页面,确实会浪费很多时间。 当你有了Browser-Sync之后,你的F5基本上可以退休了。 介绍Browser-Sync是一款基于node的模块,这样你就可以无视操作系统和浏览器,尽情使用Browser-Sync。Browser-Sync能让浏览器实时、快速响应您的文件更改(html、js、css、sass、less等)并自动刷新页面。 安装因为Browser-Sync是基于node的模块,所以我们需要使用NPM来安装Browser-Sync。1npm install -g browser-sync 用法用命令行工具进入你的项目目录下,建议进入需要监听的文件目录下,如果是监听根目录太浪费资源了。1browser-sync start --server --files "**" **是监听该目录任意文件,你也可以指定需要监听的文件名。 这样在浏览器访问localhost:3000/*.html就能实时在页面中响应你代码的变化。 访问localhost:3001,你就会感叹Browser-Sync It’s awesome !!! 详情详细说明请访问官网,配合gulp或者grunt使用更赞。]]></content>
<tags>
<tag>前端工具 BrowserSync</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Lodash学习-Collection]]></title>
<url>%2F2017%2F01%2F18%2FLodash%E5%AD%A6%E4%B9%A0-Collection%2F</url>
<content type="text"><![CDATA[上篇文章,我们已经把关于Array部分的API学习了一遍,现在我们来开始下一篇Collection。 下列API的collection参数指代Array | Object两种类型。 _.countBy _.countBy(collection, [iteratee=_.identity]) 创建一个key-value的对象,key是通过将collection按照iteratee规则迭代得到的,对应的value则是,这个key值出现了N次,value就是N。也就是迎合了API的count的意思。 例子:123456_.countBy([6.1, 4.2, 6.3], Math.floor);// => { '4': 1, '6': 2 }// The `_.property` iteratee shorthand._.countBy(['one', 'two', 'three'], 'length');// => { '3': 2, '5': 1 } 解释第一个例子吧,[6.1, 4.2, 6.3]分别执行Math.floor得到[6, 4, 6],其中4出现1次,6出现2次,所以结果是{ '4': 1, '6': 2 }。 _.every _.every(collection, [predicate=_.identity]) 如果collection全部元素满足predicate条件,返回true;否则只要出现不满足的,就返回false。predicate调用三个参数value, index, array。 例子:12345678910111213141516171819_.every([true, 1, null, 'yes'], Boolean);// => falsevar users = [ { 'user': 'barney', 'age': 36, 'active': false }, { 'user': 'fred', 'age': 40, 'active': false }];// The `_.matches` iteratee shorthand._.every(users, { 'user': 'barney', 'active': false });// => false// The `_.matchesProperty` iteratee shorthand._.every(users, ['active', false]);// => true// The `_.property` iteratee shorthand._.every(users, 'active');// => false _.filter _.filter(collection, [predicate=_.identity]) 遍历collection全部元素,返回满足predicate条件的元素组成的新数组。predicate调用三个参数value, index, array。 例子:12345678910111213141516171819var users = [ { 'user': 'barney', 'age': 36, 'active': true }, { 'user': 'fred', 'age': 40, 'active': false }];_.filter(users, function(o) { return !o.active; });// => objects for ['fred']// The `_.matches` iteratee shorthand._.filter(users, { 'age': 36, 'active': true });// => objects for ['barney']// The `_.matchesProperty` iteratee shorthand._.filter(users, ['active', false]);// => objects for ['fred']// The `_.property` iteratee shorthand._.filter(users, 'active');// => objects for ['barney'] _.find _.find(collection, [predicate=_.identity], [fromIndex=0]) 与上面_.filter不同的是_.find只返回第一个匹配的元素,可以通过fromIndex指定查找位置,默认fromIndex=0。如果没有匹配的,返回undefined。 例子:1234567891011121314151617181920var users = [ { 'user': 'barney', 'age': 36, 'active': true }, { 'user': 'fred', 'age': 40, 'active': false }, { 'user': 'pebbles', 'age': 1, 'active': true }];_.find(users, function(o) { return o.age < 40; });// => object for 'barney'// The `_.matches` iteratee shorthand._.find(users, { 'age': 1, 'active': true });// => object for 'pebbles'// The `_.matchesProperty` iteratee shorthand._.find(users, ['active', false]);// => object for 'fred'// The `_.property` iteratee shorthand._.find(users, 'active');// => object for 'barney' _.findLast _findLast(collection, [predicate=_.identity], [fromIndex=collection.length-1]) _.findLast与_find方法不同的是从右到左查找,fromIndex默认值collection.length-1。 例子:1234_.findLast([1, 2, 3, 4], function(n) { return n % 2 == 1;});// => 3 _flatMap _.flatMap(collection, [iteratee=_.identity]) collection全部元素迭代执行iteratee,将得到的元素组合成一个flattened数组(我理解的就是变成N-1维数组),iteratee调用三个参数value, index|key, collection。 例子:123456function duplicate(n) { return [n, n];}_.flatMap([1, 2], duplicate);// => [1, 1, 2, 2] 解释一下,[1, 2]调用duplicate得到[[1, 1], [2, 2]],通过_.flatMap使其扁平化[1, 1, 2, 2]。 _.flatMapDeep _.flatMapDeep(collection, [iteratee=_.identity]) 这个方法是_.flatMap的升级版,会把多维数组变成一维数组。 例子:123456function duplicate(n) { return [[[n, n]]];}_.flatMapDeep([1, 2], duplicate);// => [1, 1, 2, 2] _.flatMapDepth _.flatMapDepth(collection, [iteratee=_.identity], [depth=1]) 这个是可以指定降维次数_.flatMapDeep的版本。depth降维次数默认为1。 例子:123456function duplicate(n) { return [[[n, n]]];}_.flatMapDepth([1, 2], duplicate, 2);// => [[1, 1], [2, 2]] _.forEach _.forEach(collection, [iteratee=_.identity]) 对collection每个元素执行iteratee方法,iteratee可以调用三个参数value, index|key, collection,当collection是数组时第二个参数为index,当collection是对象时第二个参数为key。iteratee函数可以通过显式返回false提前退出迭代。 返回值:返回collection本身 例子:123456789_.forEach([1, 2], function(value) { console.log(value);});// => Logs `1` then `2`._.forEach({ 'a': 1, 'b': 2 }, function(value, key) { console.log(key);});// => Logs 'a' then 'b' (iteration order is not guaranteed). _.forEachRight _.forEachRight(collection, [iteratee=_.identity]) 和_.forEach方法的区别,collection元素从右到左执行iteratee。 例子:1234_.forEachRight([1, 2], function(value) { console.log(value);});// => Logs `2` then `1`. _.groupBy _.groupBy(collection, [iteratee=_.identity]) 对collection元素执行iteratee方法,得到key,value就是该元素。iteratee方法调用一个参数value。 返回值:返回key-value组成的新对象 例子:123456_.groupBy([6.1, 4.2, 6.3], Math.floor);// => { '4': [4.2], '6': [6.1, 6.3] }// The `_.property` iteratee shorthand._.groupBy(['one', 'two', 'three'], 'length');// => { '3': ['one', 'two'], '5': ['three'] } _.includes _.incluede(collection, value, [fromIndex=0]) 检查value是否在collection中,fromIndex指定检查的位置,默认是0。存在返回true,不存在返回false。 例子:1234567891011_.includes([1, 2, 3], 1);// => true_.includes([1, 2, 3], 1, 2);// => false_.includes({ 'a': 1, 'b': 2 }, 1);// => true_.includes('abcd', 'bc');// => true _.invokeMap _.invokeMap(collection, path, [args]) 为collection每个元素调用path方法,返回调用后的结果组成的新数组。args参数将会提供给被调用的方法。 例子:12345_.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');// => [[1, 5, 7], [1, 2, 3]]_.invokeMap([123, 456], String.prototype.split, '');// => [['1', '2', '3'], ['4', '5', '6']] _.keyBy _.keyBy(collection, [iteratee=_.identity]) 返回一个key-value对象,key是collection每个元素执行iteratee后的结果,对应的value是最后一个生成该key的collection值。iteratee调用一个参数value。 例子:12345678var array = [ { 'dir': 'left', 'code': 97 }, { 'dir': 'right', 'code': 100 }, { 'dir': 'right', 'code': 99}];_.keyBy(array, 'dir');// => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 99 } } _.map _.map(collection, [iteratee=_.identity]) 这个就比较简单了,为collection的每个元素执行iteratee方法,得到的结果映射成一个新的数组。 例子:123456789101112131415161718function square(n) { return n * n;}_.map([4, 8], square);// => [16, 64]_.map({ 'a': 4, 'b': 8 }, square);// => [16, 64] (iteration order is not guaranteed)var users = [ { 'user': 'barney' }, { 'user': 'fred' }];// The `_.property` iteratee shorthand._.map(users, 'user');// => ['barney', 'fred'] _.orderBy _.orderBy(collection, [iteratees=[_.identity], [orders]) 这个方法很像_.sortBy,不过_.orderBy允许指定排序方式iteratees。orders默认是asc(升序),也可以指定为desc,返回一个新的有序的数组。 例子:12345678910var users = [ { 'user': 'fred', 'age': 48 }, { 'user': 'barney', 'age': 34 }, { 'user': 'fred', 'age': 40 }, { 'user': 'barney', 'age': 36 }];// Sort by `user` in ascending order and by `age` in descending order._.orderBy(users, ['user', 'age'], ['asc', 'desc']);// => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] 这个例子,排序关键字有两个user和age,两个for循环,排序完成一个,在做另一个排序。 _.partition _.partition(collection, [predicate=_.identity]) 将collection分成两组,一组是执行predicate返回true,另一组是返回false。返回的结果应该是一个二维数组。 例子:1234567891011121314151617181920var users = [ { 'user': 'barney', 'age': 36, 'active': false }, { 'user': 'fred', 'age': 40, 'active': true }, { 'user': 'pebbles', 'age': 1, 'active': false }];_.partition(users, function(o) { return o.active; });// => objects for [['fred'], ['barney', 'pebbles']]// The `_.matches` iteratee shorthand._.partition(users, { 'age': 1, 'active': false });// => objects for [['pebbles'], ['barney', 'fred']]// The `_.matchesProperty` iteratee shorthand._.partition(users, ['active', false]);// => objects for [['barney', 'pebbles'], ['fred']]// The `_.property` iteratee shorthand._.partition(users, 'active');// => objects for [['fred'], ['barney', 'pebbles']] _.reduce _.reduce(collection, [iteratee=_.identity], [accumulator]) 该方法作为一个累加器,把数组中的每个值(从左到右)执行iteratee方法开始缩减,最终变成一个值。如果accumulator没有给出,collection的第一个元素被当做初始值。iteratee调用四个参数accumulator, value, index|key, collection。 例子:12345678910_.reduce([1, 2], function(sum, n) { return sum + n;}, 0);// => 3_.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { (result[value] || (result[value] = [])).push(key); return result;}, {});// => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed) _.reduceRight _.reduceRight(collection, [iteratee=_.identity], [accumulator]) 这个方法与_.reduce()方法不同的是从右到左计算。 例子:123456var array = [[0, 1], [2, 3], [4, 5]];_.reduceRight(array, function(flattened, other) { return flattened.concat(other);}, []);// => [4, 5, 2, 3, 0, 1] _.reject _.reject(collection, [predicate=_.identity]) 这个方法与_.filter相反,返回collection执行predicate返回false条件的元素组成的新数组。 例子:12345678910111213141516171819var users = [ { 'user': 'barney', 'age': 36, 'active': false }, { 'user': 'fred', 'age': 40, 'active': true }];_.reject(users, function(o) { return !o.active; });// => objects for ['fred']// The `_.matches` iteratee shorthand._.reject(users, { 'age': 40, 'active': true });// => objects for ['barney']// The `_.matchesProperty` iteratee shorthand._.reject(users, ['active', false]);// => objects for ['fred']// The `_.property` iteratee shorthand._.reject(users, 'active');// => objects for ['barney'] _.sample _.sample(collection) 返回collection中随机的一个元素。 例子:12_.sample([1, 2, 3, 4]);// => 2 _.sampleSize _.sampleSize(collection, [n=1]) 返回collection中随机的n个数,默认n=1。 例子:12345_.sampleSize([1, 2, 3], 2);// => [3, 1]_.sampleSize([1, 2, 3], 4);// => [2, 3, 1] _.shuffle _.shuffle(collection) 把collection元素的顺序随机打乱,返回打乱后的collection。 例子:12_.shuffle([1, 2, 3, 4]);// => [4, 1, 3, 2] _.size _.size(collection) 返回collection的length,collection可以是Array|Object|string。 例子:12345678_.size([1, 2, 3]);// => 3_.size({ 'a': 1, 'b': 2 });// => 2_.size('pebbles');// => 7 _.some _.some(collection, [predicate=_.identity]) 对collection元素执行predicate,返回布尔值,迭代过程遇到返回false就停止。predicate调用三个参数value, index|key, collection。 例子:12345678910111213141516171819_.some([null, 0, 'yes', false], Boolean);// => truevar users = [ { 'user': 'barney', 'active': true }, { 'user': 'fred', 'active': false }];// The `_.matches` iteratee shorthand._.some(users, { 'user': 'barney', 'active': false });// => false// The `_.matchesProperty` iteratee shorthand._.some(users, ['active', false]);// => true// The `_.property` iteratee shorthand._.some(users, 'active');// => true _.sortBy _.sortBy(collection, [iteratee=[_.identity]]) 按照iteratee规则对collection进行排序。 例子:123456789101112var users = [ { 'user': 'fred', 'age': 48 }, { 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }, { 'user': 'barney', 'age': 34 }];_.sortBy(users, [function(o) { return o.user; }]);// => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]_.sortBy(users, ['user', 'age']);// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]]]></content>
<tags>
<tag>Lodash</tag>
</tags>
</entry>
<entry>
<title><![CDATA[jQuery Validation 简单使用]]></title>
<url>%2F2016%2F12%2F22%2FjQuery-Validation-%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8%2F</url>
<content type="text"><![CDATA[jQuery.validation是一个十分易用的表单验证插件,官方文档也比较详细。很适合刚刚上手jQuery的web前端开发者,接下来介绍我个人的使用心得。 相关资料 官网 Github 菜鸟教程 使用方法前面的资料中也有很详尽的使用方法,可以看完我写的作为深入学习的资料。 引入jquery.validate.js,还需要引入有依赖关系的jquery.js12<script src="yourDir/jquery.js"></scirpt><script src="yourDir/jquery.validate.js"></scirpt> 将校验规则写到JS文件中1234567891011121314151617$('#yourForm').validate({ rules: { key1: { validateMethod1: true, validateMethod2: false, ... }, key2: "validateMethod3" }, messages: { key1: { validateMethod1: "Tips1", validateMethod2: "Tips2" }, key2: "Tips3" }}) 以上就是最基本的一个表单验证的雏形了,也很容易看懂。不过还是稍微说一下,为<form id="yourForm"></form>绑定一个表单验证。123$('#yourForm').validate({ // do something you want}) 和其他jQuery插件一样,在上面函数内部自定义你的配置。最基本的有rules、messages,分别是验证规则,和验证未通过时的提示信息。 key 就是表单元素的name值 rules中的vaildateMethod可以设置为true/false来设置开启/关闭该方法的验证。如果该表单元素只有一个规则,也可以像key2那样直接指出。 messages将对应规则的提示信息列出来,就可以在验证未通过时提示用户。 官方提供的validateMethod能满足基本的开发需要。 required: 必须输入的字段。 remote:"check.php": 使用 ajax 方法调用 check.php 验证输入值。 email: 必须输入正确格式的电子邮件。 date: 必须输入正确格式的日期。日期校验 ie6 出错,慎用。 dateISO: 必须输入正确格式的日期(ISO),例如:2009-06-23,1998/01/22。只验证格式,不验证有效性。 number: 必须输入合法的数字。 digits: 必须输入整数。 creditcard: 必须输入合法的信用卡号。 equalTo:"#field": 必输入值必须和 #field 相同。 还有一些对于输入长度的,在此就不提及了。也可以使用jQuery.validator.addMethod()添加自定义验证方法API,接下来详细介绍。 其他配置除了rules、messages还有其他一些常用的配置项。123456789101112131415161718$('#addBookmarkForm').validate({ rules: { // }, messages: { // }, errorClass: 'modalError', errorElement: 'div', errorPlacement: function (error, element) { error.insertAfter(element.parent()); }, submitHandler: function () { // Your method }}) errorClass: 错误信息的类名。 errorElement: 修改错误信息提示消息的标签,默认是<label>。 errorPlacement: 修改错误信息显示的位置。这是一个回调函数,error参数也就是errorElement,element参数是验证的表单。通过修改插入的位置,实现不同的样式效果。 submitHandler: 当所有验证规则通过后执行的回调函数,做ajax还是别的什么,取决于你的业务场景。 触发方式说了这么半天,还没有谈到怎么触发表单验证,其实很简单就是submit,你可以用多种方式实现submit。 type="submit"的<button>或者<input> $('#yourForm').submit() 增加自定义验证规则先贴一段我自己业务需要的验证规则12345678910111213141516171819202122232425262728293031323334validateMethod: function () { var regEx1 = new RegExp(/[&\(\\\"\'\%\)]/); var regEx2 = new RegExp(/^(http|https):\/\/.{1,250}/); var regEx3 = new RegExp(/^\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/.{1,255}$/); var regEx4 = new RegExp(/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/); jQuery.validator.addMethod('bookmarkRule1', function (value, element, parans) { return this.optional( element ) || !regEx1.test(value); }, 'URL should not include char & ( ) \\ % \" \' '); jQuery.validator.addMethod('bookmarkRule2', function (value, element, parans) { if ($('#type').val() == '0') { return this.optional( element ) || regEx2.test(value); } else { return true; } }, 'Please input correct Web URL!'); jQuery.validator.addMethod('bookmarkRule3', function (value, element, parans) { if ($('#type').val() == '1') { return this.optional( element ) || regEx3.test(value); } else { return true; } }, 'Please input correct File URL!'); jQuery.validator.addMethod('bookmarkRule4', function (value, element, parans) { if ($('#type').val() == '2') { return this.optional( element ) || regEx4.test(value); } else { return true; } }, 'Please input correct Desktop!');} 我相信你根据API,就能写出自己需要的验证规则。 结语希望看完本文章对你能有所帮助。]]></content>
<tags>
<tag>jQuery Plugin</tag>
</tags>
</entry>
<entry>
<title><![CDATA[2016年书单]]></title>
<url>%2F2016%2F11%2F20%2F2016%E5%B9%B4%E4%B9%A6%E5%8D%95%2F</url>
<content type="text"><![CDATA[2016年书单(6/12)2016年计划读12本书,平均每个月一本,然后把书中值得分享的语句感想发读书笔记。今年6月底毕业,估计今年12本书的计划不大可能实现了,但也会在余下的时间多读书,有句话不是说:“人丑就要多读书”。 《Just For fun》 《人类简史》 《黑客与画家》 《ECMAScript 6入门》 《ES 6标准入门》 (在读) 《深入浅出nodejs》 (在读) 《图解CSS3》 (在读) 《马克·扎克伯格:后乔布斯时代的传奇》 《希拉里:通向白宫的最后一英里》 《证券分析(上)》 (在读)]]></content>
<tags>
<tag>阅读</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Lodash学习--Array]]></title>
<url>%2F2016%2F11%2F08%2FLodash%E5%AD%A6%E4%B9%A0-Array%2F</url>
<content type="text"><![CDATA[Lodash是一个具有一致接口、模块化、高性能等特性的 JavaScript 工具库。还有一个类似的库是underscore,不过underscore不能按需加载。朋友说:这种工具库,平时都是先用先写的。不过我觉得,还是很有必要学习一下的,看下源码的实现。本文主要是对Lodash的API,用自己理解的方式做一下说明。可能理解有误,不过还是要记录下来,当再用的时候有据可查。 _.chunk _.chunk(array, [size=1]) 将数组进行分块,按照size指定的长度,默认长度1 _.compact _.compact(array) 剔除数组中没有意义的值,比如false, null, 0, "", undefined 和 NaN _.concat _.concat(array, [values]) 创建一个新的数组来保存原始数组,增加值/数组之后的结果 例子:12345678var array = [1];var other = _.concat(array, 2, [3], [[4]]);console.log(other);// => [1, 2, 3, [4]]console.log(array);// => [1] _.difference _.difference(array, [values]) 这个函数就比较复杂了,就想看一下源码,发现嵌套的函数太多。就投机取巧直接测试吧。这个函数,大概的作用就是将array和[values]进行比较,将array中比[values]中多出的值,保存到一个新的数组中。 例子:12345678//官网就比较敷衍了_.difference([2, 1], [2, 3]);// => [1]//下面是我做的一些测试_.difference([1, 1, "1", 2, 2], [1]);// => ["1", 2, 2]// 只要array中比[values]中多出的值,都会返回,不管个数出现了几次 _.differenceBy _.differenceBy(array, [values], [iteratee=_.identity]) 这个方法就是在_.difference方法的基础上,增加了一个参数。反正是看了一会,才看明白这个第三个参数,怎么工作的。 例子:123456_.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);// => [1.2]// The `_.property` iteratee shorthand._.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');// => [{ 'x': 2 }] 第一个官方例子,就是看了半天没看懂。我以为是先_.difference在对得到的结果进行Math.floor运算,其实是想错了。 如果这么工作的话,就没必要设计_.differenceBy了,直接用_.difference.xx()就可以,所以我一开始的想当然是有问题的。正确地工作步骤是,对前两个参数,分别执行第三个参数过滤,然后再比较找出array中比[values]中多出的部分,然后返回这些多余部分的原始值的一个数组。就拿第一个说吧,执行过滤之后是[2,1]和[2,3],应该是返回[1]的原始值[1.2],就酱。 _.differenceWith _.differenceWith(array, [values], [comparator]) 没太看懂。。。 _.drop _.drop(array, [n=1]) 我理解的是抛弃前n个数组元素,返回剩下的数组元素,默认抛弃第一个。 例子:1234567891011_.drop([1, 2, 3]);// => [2, 3]_.drop([1, 2, 3], 2);// => [3]_.drop([1, 2, 3], 5);// => []_.drop([1, 2, 3], 0);// => [1, 2, 3] _.dropRight _.dropRight(array, [n=1]) _.dropRight和_.drop功能是一样的,就是_.drop是从后往前抛弃n个数组元素,默认抛弃最后一个。 例子:1234567891011_.dropRight([1, 2, 3]);// => [1, 2]_.dropRight([1, 2, 3], 2);// => [1]_.dropRight([1, 2, 3], 5);// => []_.dropRight([1, 2, 3], 0);// => [1, 2, 3] _.dropRightWhile _.dropRightWhile(array, [predicate=_.identity]) 从尾端查询数组array,第一个不满足predicate条件的元素开始截取数组。参数predicate提供的是一个属性名称,就通过提供的参数使用_.property方法返回一个回调函数。参数predicate提供的是一个对象,就用_.matches方法匹配相同的元素,相同返回true,不同返回false。参数predicate也可以提供一个函数,该函数有三个参数value, index, array _.dropRightWhile这个函数还牵扯到另外两个函数,_.property和_.matches。 例子:1234567891011121314151617181920var users = [ { 'user': 'barney', 'active': true }, { 'user': 'fred', 'active': false }, { 'user': 'pebbles', 'active': false }];_.dropRightWhile(users, function(o) { return !o.active; });// => objects for ['barney']// The `_.matches` iteratee shorthand._.dropRightWhile(users, { 'user': 'pebbles', 'active': false });// => objects for ['barney', 'fred']// The `_.matchesProperty` iteratee shorthand._.dropRightWhile(users, ['active', false]);// => objects for ['barney']// The `_.property` iteratee shorthand._.dropRightWhile(users, 'active');// => objects for ['barney', 'fred', 'pebbles'] _.dropWhile _.dropWhile(array, [predicate=_.identity]) 这个方法与上面_.dropRightWhile不同之处,是从数组的首端开始查询。 _.fill _.fill(array, value, [start=0], [end=array.length]) 将value填充到array中,start默认为0,end默认为array.length。这个就比较好理解了。 例子:1234567891011var array = [1, 2, 3];_.fill(array, 'a');console.log(array);// => ['a', 'a', 'a']_.fill(Array(3), 2);// => [2, 2, 2]_.fill([4, 6, 8, 10], '*', 1, 3);// => [4, '*', '*', 10] _.findIndex _.findIndex(array, [predicate=_.identity], [fromIndex=0]) 返回满足predicate条件的一个array数组的index,也可以指定从哪里开始查询,没找到满足条件的返回-1 例子:1234567891011121314151617181920var users = [ { 'user': 'barney', 'active': false }, { 'user': 'fred', 'active': false }, { 'user': 'pebbles', 'active': true }];_.findIndex(users, function(o) { return o.user == 'barney'; });// => 0// The `_.matches` iteratee shorthand._.findIndex(users, { 'user': 'fred', 'active': false });// => 1// The `_.matchesProperty` iteratee shorthand._.findIndex(users, ['active', false]);// => 0// The `_.property` iteratee shorthand._.findIndex(users, 'active');// => 2 _.findLastIndex _.findLastIndex(array, [predicate=_.identity], [fromIndex=array.length -1]) 和_.findIndex基本相同,不过_.findLastIndex是从尾部往首部开始查找。 例子:1234567891011121314151617181920var users = [ { 'user': 'barney', 'active': true }, { 'user': 'fred', 'active': false }, { 'user': 'pebbles', 'active': false }];_.findLastIndex(users, function(o) { return o.user == 'pebbles'; });// => 2// The `_.matches` iteratee shorthand._.findLastIndex(users, { 'user': 'barney', 'active': true });// => 0// The `_.matchesProperty` iteratee shorthand._.findLastIndex(users, ['active', false]);// => 2// The `_.property` iteratee shorthand._.findLastIndex(users, 'active');// => 0 _.flatten _.flatten(array) 这个函数的作用是将array减少一个维度。 例子:12_.flatten([1, [2, [3, [4]], 5]]);// => [1, 2, [3, [4]], 5] _.flattenDeep _.flattenDeep(array) 相当于递归执行_.flatten,最终将array变成一维数组。 例子:12_.flattenDeep([1, [2, [3, [4]], 5]]);// => [1, 2, 3, 4, 5] _.flattenDepth _.flattenDepth(array, [depth=1]) 相当于指定执行_.flattenDepth``depth次,默认depth为1。 例子:1234567var array = [1, [2, [3, [4]], 5]];_.flattenDepth(array, 1);// => [1, 2, [3, [4]], 5]_.flattenDepth(array, 2);// => [1, 2, 3, [4], 5] _.fromPairs _.fromPairs(pairs) 将pairs键值对转换成一个对象。 例子:12345_.fromPairs([['a', 1], ['b', 2]]);// => { 'a': 1, 'b': 2 }_.fromPairs([['a', 1], ['b', 2], ['c', ['d', 4]]]);// => { 'a': 1, 'b': 2, 'c': [ 'd', 4 ] } _.head _.head(array) 返回array的第一个元素,别名_.first 例子:12345678_.head([1, 2, 3]);// => 1_.head([]);// => undefined_.head([[1, 4], 2, 3]);// => [1, 4] _.last _.last(array) 返回array的最后一个元素。 例子:12_.last([1, 2, 3]);// => 3 _.nth _.nth(array, [n=0]) 获取指定index的array数组元素。 例子:1234567var array = ['a', 'b', 'c', 'd'];_.nth(array, 1);// => 'b'_.nth(array, -2);// => 'c'; _.tail _.tail(array) 返回去除第一个元素的数组。 例子:12_.tail([1, 2, 3]);// => [2, 3] _.indexOf _.indexOf(array, value, [fromIndex=0]) 在array中查找value,返回找到的第一个匹配的index,没找到则返回-1,第三个参数fromIndex指定查找的起始位置,默认为0; 例子:123456789_.indexOf([1, 2, 1, 2], 2);// => 1_.indexOf([1, 2, 1, 2], 3);// => -1// Search from the `fromIndex`._.indexOf([1, 2, 1, 2], 2, 2);// => 3 _.lastIndexOf _.lastIndexOf(array, value, [fromIndex=array.length-1]) 和_.indexOf方法一样,不过是从尾部开始查找。 例子:123456_.lastIndexOf([1, 2, 1, 2], 2);// => 3// Search from the `fromIndex`._.lastIndexOf([1, 2, 1, 2], 2, 2);// => 1 _.initial _.initial(array) 去除array最后一个元素,并返回。 例子:12345_.initial([1, 2, 3]);// => [1, 2]_.initial([1, 2, 3, [4, 5]]);// => [1, 2, 3] _.intersection _.intersection([arrays]) 取出各数组中全等的元素,使用SameValueZero方式平等比较。 例子:12345_.intersection([2, 1], [2, 3]);// => [2]_.intersection([1, 2], [4, 2], [2, 1]);// => [2] _.intersectionBy _.intersectionBy([arrays], [iteratee=_.identity]) _.intersectionBy就是在_.intersection的基础上接受了一个iteratee迭代器,生成了一个比较的标准,类似于_.differenceBy。 例子:123456_.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor);// => [2.1]// The `_.property` iteratee shorthand._.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');// => [{ 'x': 1 }] _.intersectionWith _.intersectionWith([arrays], [comparator]) 这个函数和_.differenceWith差不多,一样没太看懂。先略过。 _.join _.join(array, [separator=',']) 将array转换成字符串类型并通过separator分隔开,默认使用,分隔。 例子:12345_.join(['a', 'b', 'c'], '~');// => 'a~b~c'_.join(['a', 'b', 'c', ['d', 'e']], '-');// => 'a-b-c-d,e' _.pull _.pull(array, [values]) 移除array中所有的指定values,需要注意的是这个函数会对原始array做修改。 例子:12345var array = ['a', 'b', 'c', 'a', 'b', 'c'];_.pull(array, 'a', 'c');console.log(array);// => ['b', 'b'] _.pullAll _.pullAll(array, values) _.pullAll方法应该是_.pull方法的升级,这个方法是在Lodash 4.0.0中提出的。 例子:12345var array = ['a', 'b', 'c', 'a', 'b', 'c'];_.pullAll(array, ['a', 'c']);console.log(array);// => ['b', 'b'] _.pullAllBy _.pullAllBy(array, values, [iteratee=_.identity]) _.pullAllBy方法很像_.pullAll方法,除了可以接受一个迭代器iteratee,为每一个数组元素执行迭代器并生成一个比较的标准,这个迭代器调用一个参数value。注:原始数组改变 例子:1234567891011var array1 = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];_.pullAllBy(array1, [{ 'x': 1 }, { 'x': 3 }], 'x');console.log(array);// => [{ 'x': 2 }]var array2 = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }, { 'y': 4}];_.pullAllBy(array, [{ 'x': 1 }], 'y');console.log(array);// => [{ 'y': 4 }] _.pullAllWith _.pullAllWith(array, values, [comoarator]) 这个跳过。。。 _.pullAt _.pullAt(array, [indexes]) 移除相应index的元素,返回被移除元素的数组。注:原始数组改变 例子:12345678var array = ['a', 'b', 'c', 'd'];var pulled = _.pullAt(array, [1, 3]);console.log(array);// => ['a', 'c']console.log(pulled);// => ['b', 'd'] _.remove _.remove(array, [predicate=_.identity]) 移除所有predicate返回真的数组元素,并返回被移除的数组元素。predicate调用三个参数value, index, array。 例子:12345678910var array = [1, 2, 3, 4];var evens = _.remove(array, function(n) { return n % 2 == 0;});console.log(array);// => [1, 3]console.log(evens);// => [2, 4] _.reverse _.reverse(array) 这个就比较简单了,是一个反序排列的方法,也会对原始方法进行更改 例子:1234567var array = [1, 2, 3];_.reverse(array);// => [3, 2, 1]console.log(array);// => [3, 2, 1] _.slice _.slice(array, [start=0], [end=array.length]) 对数组进行分割。 例子:1234var array = [1, 2, 3];_.slice(array, 1, 2);// => [2] _.sortedIndex _.sortedIndex(array, value)向一个有序数组中插入一个value,将返回这个值插入之后的有序位置。(使用二分查找) 例子:12345_.sortedIndex([30, 50], 40);// => 1_.sortedIndex([30, 50], 30);// => 0 _.sortedIndexBy _.sortedIndexBy(array, value, [iteratee=_.identity]) 凡是带By的方法方法,都是这种结构的函数。_.sortIndexBy比_.sortIndex方法多一个参数,接收一个迭代器iteratee去计算排序,这个iteratee调用一个参数value。 例子:12345678var objects = [{ 'x': 4 }, { 'x': 5 }];_.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });// => 0// The `_.property` iteratee shorthand._.sortedIndexBy(objects, { 'x': 4 }, 'x');// => 0 _.sortedIndexOf _.sortedIndexOf(array, value) 这个方法很像_.indexOf,_.sortedIndexOf是对一个有序数组进行二分查找。 例子:12_.sortedIndexOf([4, 5, 5, 5, 6], 5);// => 1 _.sortedLastIndex _.sortedLastIndex(array, value) 这个方法很像_.sortedIndex,这个方法在保持有序的前提下会把value插进最大的那个位置。 例子:12_.sortedLastIndex([4, 5, 5, 5, 6], 5);// => 4 _.sortedLastIndexBy _.sortedLastIndexBy(array, value, [iteratee=_.identity]) 这个方法很像_.sortedLastIndex,只不过多了一个参数iteratee,这个迭代器为每个元素值计算他们的排序,这个迭代器调用一个参数value。返回应该被插入后的数组下标。 例子:12345678var objects = [{ 'x': 4 }, { 'x': 5 }];_.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });// => 1// The `_.property` iteratee shorthand._.sortedLastIndexBy(objects, { 'x': 4 }, 'x');// => 1 _.sortedLastIndexOf _.sortedLastIndexOf(array, value) 这个方法很像_.lastIndexOf,只不过它对一个有序数组进行二分查找。 例子:12_.sortedLastIndexOf([4, 5, 5, 5, 6], 5);// => 3 _.sortedUniq _.sortedUniq(array) 这个方法很像_.uniq,这个方法是为了有序数组设计且优化的,返回一个去重的数组。 例子:12_.sortedUniq([1, 1, 2]);// => [1, 2] _.sortedUniqBy _.sortedUniqBy(array, [iteratee]) 这个方法很像_.uniqBy,它返回经过iteratee计算之后,去除重复值,只返回重复值的第一个原值和不重复值组成的有序数组。 例子:12_.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);// => [1.1, 2.3] _.take _.take(array, [n=1]) 创建一个分割后的数组,从array数组的开始到第n个元素。 例子:1234567891011_.take([1, 2, 3]);// => [1]_.take([1, 2, 3], 2);// => [1, 2]_.take([1, 2, 3], 5);// => [1, 2, 3]_.take([1, 2, 3], 0);// => [] _.takeRight _.takeRight(array, [n=1]) 创建一个分割后的数组,从array数组的结尾开始,分割n个元素出来。 例子:1234567891011_.takeRight([1, 2, 3]);// => [3]_.takeRight([1, 2, 3], 2);// => [2, 3]_.takeRight([1, 2, 3], 5);// => [1, 2, 3]_.takeRight([1, 2, 3], 0);// => [] _.takeRightWhile _.takeRightWhile(array, [predicate=_.identity]) 同样是从array结尾开始分割数组,不过是通过predicate控制,直到返回falsey停止。predicate调用三个参数value, index, array 例子:1234567891011121314151617181920var users = [ { 'user': 'barney', 'active': true }, { 'user': 'fred', 'active': false }, { 'user': 'pebbles', 'active': false }];_.takeRightWhile(users, function(o) { return !o.active; });// => objects for ['fred', 'pebbles']// The `_.matches` iteratee shorthand._.takeRightWhile(users, { 'user': 'pebbles', 'active': false });// => objects for ['pebbles']// The `_.matchesProperty` iteratee shorthand._.takeRightWhile(users, ['active', false]);// => objects for ['fred', 'pebbles']// The `_.property` iteratee shorthand._.takeRightWhile(users, 'active');// => [] _.takeWhile _.takeWhile(array, [predivate=_.identity]) 是从array开头开始分割数组,不过是通过predicate控制,直到返回falsey停止。predicate调用三个参数value, index, array 例子:1234567891011121314151617181920var users = [ { 'user': 'barney', 'active': false }, { 'user': 'fred', 'active': false}, { 'user': 'pebbles', 'active': true }];_.takeWhile(users, function(o) { return !o.active; });// => objects for ['barney', 'fred']// The `_.matches` iteratee shorthand._.takeWhile(users, { 'user': 'barney', 'active': false });// => objects for ['barney']// The `_.matchesProperty` iteratee shorthand._.takeWhile(users, ['active', false]);// => objects for ['barney', 'fred']// The `_.property` iteratee shorthand._.takeWhile(users, 'active');// => [] _.union _.union([arrays]) 创建一个没有重复值的数组,组合所有被传入的数组元素。 例子:12_.union([2], [1, 2]);// => [2, 1] _.unionBy _.unionBy([arrays], [iteratee=_.identity]) 通过iteratee对每个元素值进行执行,生成一个唯一性的标准,并选择第一个出现的值,作为要返回的值,去除重复的元素。iteratee调用一个参数value。 例子:123456_.unionBy([2.1], [1.2, 2.3], Math.floor);// => [2.1, 1.2]// The `_.property` iteratee shorthand._.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');// => [{ 'x': 1 }, { 'x': 2 }] _.unionWith _.unionWith([arrays], [comparator]) 这个没太看懂。。。 例子:12345var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];_.unionWith(objects, others, _.isEqual);// => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] _.uniq _.uniq(array) 数组去重。 例子:12_.uniq([2, 1, 2]);// => [2, 1] _.uniqBy _.uniqBy(array, [iteratee=_.identity]) 这个方法是有条件的数组去重,通过iteratee迭代器生成一个唯一性的标准。iteratee调用一个参数value. 例子:123456_.uniqBy([2.1, 1.2, 2.3], Math.floor);// => [2.1, 1.2]// The `_.property` iteratee shorthand._.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');// => [{ 'x': 1 }, { 'x': 2 }] _.uniqWith _.uniqWith(array, [comparator]) 没太分清_.uniqWith和_.uniqBy之间有什么区别。_.uniqWith传入的是一个比较器。comparator调用两个参数arrVal, othVal。 例子:1234var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];_.uniqWith(objects, _.isEqual);// => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }] _.without _.without(array, [values]) 创建一个新的数组,去除所有传入的values。 例子:12_.without([2, 1, 2, 3], 1, 2);// => [3] _.xor _.xor([arrays]) xor就是异或,相同为0,不同为1,1为true,应该被返回。创建一个唯一值的数组,返回被给数组之间对称差(没有交集的部分)的元素。结果值的顺序由它们在数组中出现的顺序确定。 例子:12345_.xor([2, 1, 4], [2, 3, 5]);// => [1, 4, 3, 5]_.xor([2, 2, 3], [4, 4, 5, 6]);// => [2, 3, 4, 5, 6] _.xorBy _.xorBy([arrays], [iteratee=_.identity]) 有条件的_.xor方法,和所有_.xxBy方法一样,接收一个iteratee方法生成一个标准,iteratee接受一个参数value。 例子:123456_.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor);// => [1.2, 3.4]// The `_.property` iteratee shorthand._.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');// => [{ 'x': 2 }] _.xorWith _.xorWith([arrays], [comparator]) 这个方法类似于_.xor,除了它接受比较器comparator,它被调用来比较数组的元素。结果值的顺序由它们在数组中出现的顺序确定。comparator调用两个参数arrVal,othVal。 例子:12345var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];_.xorWith(objects, others, _.isEqual);// => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] _.zip _.zip([arrays]) 创建一个元素组数组,把每个传入数组,第一个元素组合到一起,第二个元素组合在一起,以此类推。 例子:12345_.zip(['a', 'b'], [1, 2], [true, false]);// => [['a', 1, true], ['b', 2, false]]_.zip(['a', 'b'], [1], [true, false]);// => [['a', 1, true], ['b', undefined, false]] _.unzip _.unzip(array) 就是把_.zip函数打包好的,或是元素组数组,对其进行解包。 例子:12345var zipped = _.zip(['a', 'b'], [1, 2], [true, false]);// => [['a', 1, true], ['b', 2, false]]_.unzip(zipped);// => [['a', 'b'], [1, 2], [true, false]] _.zipObject _.zipObject([props=[]], [values=[]]) 这个方法很像_.fromPairs,_.zipObject接受两个数组,一个属性数组和一个相应的对应值数组。 例子:12345_.zipObject(['a', 'b'], [1, 2]);// => { 'a': 1, 'b': 2 }_.zipObject(['a', 'b'], [1]);// => { 'a': 1, 'b': undefined } _.zipObjectDeep `_.zipObjectDeep([props=[]], [values=[]]) 这个方法像_.zipObject方法一样,不过它支持属性路径property paths 例子:12_.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);// => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } } _.zipWith _.zipWith([arrays], [iteratee=_.identity]) 这个方法像类似_.zip,接受一个迭代器iteratee去指定怎么如何组合分组值。这个迭代器为每个组的元素调用...group,还是看例子比较直观。 例子:1234_.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) { return a + b + c;});// => [111, 222] _.unzipWith _.unzipWith(array, [iteratee=_.identity]) 这个方法很像_.unzip,它接受一个迭代器iteratee去指定怎样重组组合值。iteratee调用一个参数...group。 例子:12345var zipped = _.zip([1, 2], [10, 20], [100, 200]);// => [[1, 10, 100], [2, 20, 200]]_.unzipWith(zipped, _.add);// => [3, 30, 300]]]></content>
<tags>
<tag>lodash</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JavaScript对于cookie的读、写操作]]></title>
<url>%2F2016%2F10%2F27%2FJavaScript%E5%AF%B9%E4%BA%8Ecookie%E7%9A%84%E8%AF%BB%E3%80%81%E5%86%99%E6%93%8D%E4%BD%9C%2F</url>
<content type="text"><![CDATA[前端开发中,不可避免的总要接触session、cookie这些网络数据打交道进行一些操作。所以说作为一个前端er,如果不会操作cookie,怎么好意思呐。接下来,我们就来讲讲对于cookie的读写… cookie的操作写入cookie12345function setCookie(cName, cValue, days) { var expires = new Date(); expires.setTime(expires.getTime() + parseInt(days) * 24 * 60 * 60 * 1000); document.cookie = cName + "=" + escape(cValue) + ";expires=" + expires.toGMTString()+";path=/;domain=xxx.cn"; }; 这个函数也十分简单,我们可以设置cookie的名字、值以及有效期。注:我们并没有重写这个cookie,这是添加了我们设置的cookie。 expires: cookie的过期时间,注意这里要使用格林威治时间 path: 这个参数表示cookie保存的路径,如果没有给出的话会保存为当前站点的,如果给出值”/“的话会保存到当前虚拟目录 domain: 这个参数有点类似于session的保存路径,默认情况下保存在当前客户端,也可以自定义到其他位置 读取cookie123456function getCookie(name){ var arr = document.cookie.match(new RegExp("(^| )"+name+"=([^;]*)(;|$)")); if(arr != null) return decodeURI(arr[2]); return null; } 说一说,上面的正则表达式吧。1new RegExp("(^| )"+name+"=([^;]*)(;|$)") ^以什么开头,所以(^| )"+name+"就是以"name"或者" name"开头的 [^;]*匹配除了;以外的任意字符 $以什么结尾,所以(;|$)就是以";"或者""结尾 decodeURI()函数可对encodeURI() 函数编码过的 URI 进行解码 上述例子,还使用了一个技巧就是()进行分组,得到的正则表达式结果就是一个Array类型,每个()包括的部分都是数组的一部分,index从1开始。 arr[0]是匹配的整个结果。 arr[1]是""或者" " arr[2]是我们要获取name的value值 arr[4]是是""或者";" 结语介绍JS操作cookie的文章网上也有很多,本文不胜详尽,只是列出了我熟悉的一部分。]]></content>
<tags>
<tag>JavaScipt</tag>
<tag>cookie</tag>
</tags>
</entry>
<entry>
<title><![CDATA[(译)学习如何构建自动化、跨浏览器的JavaScript单元测试]]></title>
<url>%2F2016%2F10%2F25%2F(%E8%AF%91)%E5%AD%A6%E4%B9%A0%E5%A6%82%E4%BD%95%E6%9E%84%E5%BB%BA%E8%87%AA%E5%8A%A8%E5%8C%96%E3%80%81%E8%B7%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84JavaScript%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%2F</url>
<content type="text"><![CDATA[作者:Philip Walton译者:Yeaseon原文链接:点此查看 我们都知道在多个浏览器中测试我们的代码是多么的重要。至少在我们发布第一个项目的时候,我认为我们在网络开发社区做大部分工作还是相当不错的。我们做的不够好的工作是测试代码时每一次做出的改变。我个人对此感到很惭愧。我已经把“学习如何构建自动化、跨浏览器的JavaScript的单元测试”列在我的年度to-do清单中,但我每一次坐下来真正想要做的时候,我又退却了。虽然我肯定这一部分原因是因为我的懒惰,同时我认为这也是由于缺乏良好的可用信息在这个主题上。有许多工具和框架(例如 Karma)宣称“要使自动化的JavaScript测试变得简单”,但以我的经验看来这些工具引入的复杂性比他们摆脱的复杂性更多。在我的工作经验中,如果你是一个专家这些工具“能工作”的很好,但对于一个初学者是很糟糕的。我想要真正了解的是这个流程是如何在引擎中工作的,以便在它出现问题的时候(总会出现问题的),我能解决它。对我来说,充分了解这些是如何工作的最好方法就是尝试从头开始重新创建它。所以我决定去构建我自己的测试工具,然后把我的所学分享到社区中。 手工测试流程在我解释自动化过程之前,我认为最重要的是确保我们都在同一页面上进行手工测试工作。 毕竟,自动化是关于使用机器来关闭负载的重复部分的现有工作流程。如果你在充分理解手工过程之前尝试去开始自动化,它也不会像你理解了自动化过程一样。 在手工过程中,你写了一个你的测试文件,它可能看起来像是: 1234567891011var assert = require('assert');var SomeClass = require('../lib/some-class');describe('SomeClass', function() { describe('someMethod', function() { it('accept thing A and transforms it into thing B',function() { var sc = new SomeClass(); assert.equal(sc.someMethod('A'), 'B'); }); });}); 这个例子用了Mocha和Node.js 资源模块,但是重要的不是你是用的测试库或者断言库,它可以使任意一个。 在Mocha中运行Node.js,在你终端通过命令行你就能运行这个测试: mocha test/some-class-test.js 你需要一个带有<script>标签的HTML文件加载这段脚本,才能在浏览器运行这个测试,浏览器并不认识require声明,你需要一个像是browserify或者webpack的模块打包工具去解决这些依赖。 browserify test/*-test.js > test/index.js 像是browserify或是webpack的模块打包工具的好处就是它能整合你的所有测试(也包括依赖)到一个单一的文件中,这样就能很容易加载到你的测试页面。 一个用Mocha写的典型测试文件看起来像是这样的: 123456789101112131415161718192021222324<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>Tests<title> <link href="../node_modules/mocha/mocha.css" rel="stylesheet" /> <script src="../node_modules/mocha/mocha.js"></script></head><body> <!-- A container element for the visual Mocha results --> <div id="mocha"></div> <!-- Mocha setup and initiation code --> <script> mocha.setup('bdd'); window.onload = function() { mocha.run(); }; </script> <!-- The script under test --> <script src="index.js></script></body></html> 如果你不使用Node.js,那么你的起点看起来已经很像这个HTML文本了,唯一不同的是你的依赖可能需要列成一个个单独的<script>标签。 错误检测如果一个测试由于是断言不正确,你的断言库任何时间都会抛出一个错误,这个时候你的测试框架就能发现这个错误。测试框架运行在每个测试的try/catch中来捕获可能会抛出的错误,这些错误报告会显示在你的页面中或是在console中显示这些log。 大多数的测试框架(像是Mocha)将会提供钩子,这样你就能在测试过程中让页面中的其他脚本访问测试结果。这是一个自动化测试过程的一个关键特征,因为为了自动化工作,自动化脚本需要能够提取测试脚本的结果。 手工方式的优点在浏览器中手工运行测试的最大好处是,假如你的一个测试失败了你能用浏览器的开发者工具去调试它。 像是这样的一个简单例子: 12345678910describe('SomeClass', () => { describe('someMethod', () => { it('accepts thing A and transforms it into thing B', () => { const sc = new SomeClass(); debugger: assert.equal(sc.someMethod('A'), 'B'); )}; )};)}; 现在当你重新打包并刷新浏览器打开开发者工具,你就可以通过你的代码,很容易定位到问题的根源所在。 相比之下,大多数流行的自动化测试框架使这变得很困难!它们提供的方便之处是它们捆绑了你的单元测试并且为你创建一个宿主的HTML页面。 在你的任何一个测试都不会失败的时候,这是很好的方式。因为当它们这样做时,就没有办法轻松地reproduce和本地调试。 自动化流程手工流程有它的优点,同时也有一些缺点。打开几个浏览器去运行测试,每次你想做出改动的时候都会变得繁琐且容易出错。更不用说,我们大部分人没有安装每一个浏览器的每一个版本到我们的本地开发机器上。 如果你在认真的测试你的代码,并希望确保它的每一个变化都做适当的,那么你需要自动化这个流程。 无论你是多么的自觉,手动测试是太容易忘记或忽略,最终它不会充分利用你的时间。 但是自动化测试同样也有它的不足。过于频繁的自动测试工具引入了一个全新的问题。轻微不同的构建,测试就会变得不同,测试失败的话面临的将是痛苦的调试。 当我计划如何构建我的自动化测试系统的时候,我不想再掉进这个陷阱和失去手工测试流程的便利性。所以我决定在开始之前做一个需求列表。 毕竟,一个自动化系统如果引入了新的令人头疼的麻烦和复杂性,那它就不是一个成功的自动化系统。 需求 我需要能够使用命令行运行测试 我需要能够在本地调试失败测试 我需要所有必需的依赖通过npm运行测试就能被安装,所以任何人查看我的代码就能很简单的运行,通过npm install && npm test 我需要运行在CI机器上的测试流程和运行在我的开发机器一样简单。这样构建方式是相同的,并且无需检查新的变化就能调试错误。 我需要所有的测试我(或者任意人)提交新的变化或者拉取请求都能在任何时间自动化运行。 有了这个粗略的列表之后,下一步就是深入到在主流的云测试如何自动化,跨浏览器测试的工作。 如何进行云测试有很多云测试的供应商,每个供应商都有自己的长处和短处。我是一个开源作者,所以我只看那些提供开源项目的供应商,它们之中,只有Sauce Labs是唯一一个不需要我邮箱支持就能启动一个新的开源账户。 更令我吃惊的是当我真正开始钻研Sauce Labs关于JavaScript单元测试的文档是有多么简单。由于好多测试框架都有声称让单元测试变得简单,我认为这真的很难! 我前面强调了一点就是,我不想我的自动化流程和我的手工流程有什么根本上的不同。事实证明,Sauce Labs提供的自动化方法真的很像我的手工方法。 这里是所涉及的步骤:1.你给Sauce Labs一个你测试页面的URL以及你要运行的测试的浏览器/平台列表。2.Sauce Labs使用selenium webdriver去加载你给它的每一个浏览器和平台的组合的测试页面。3.WebDriver检查网页是否测试失败,并将结果存储。4.Sauce Labs将有用的结果给你。 这真的很简单。 我错误地假设你不得不把你的JavaScript代码给Sauce Labs,并且它将会运行在它的机器上,而不是它们只是去访问你给它们的URL。这样的话看起来就像手工流程了;唯一不同的是Sauce Labs去打开所有的浏览器并为你记录下结果。 API方法Sauce Labs有两个运行单元测试的API方法: StartJS Unit Tests Get JS Unit Test Status StartJS Unit Tests方法在你指定的浏览器/平台启动一个测试页面。 文档给了一个使用curl的例子: 12345curl https://saucelabs.com/rest/v1/SAUCE_USERNAME/js-tests \ -X POST \ -u SAUCE_USERNAME:SAUCE_ACCESS_KEY \ -H 'Content-Type: application/json' \ --data '{"url": "https://example.com/tests.html", "framework": "mocha", "platforms": [["Windows 7", "firefox", "27"], ["Linux", "chrome", "latest"]]}' 因为这是JavaScript单元测试,我将给你一个使用node模块request的例子,如果你正在用Node.js它可能更接近你最终要做的: 1234567891011121314151617181920212223request({ url: `https://saucelabs.com/rest/v1/${username}/js-tests`, method: 'POST', auth: { username: process.env.SAUCE_USERNAME, password: process.env.SAUCE_ACCESS_KEY }, json: true, body: { url: 'https://example.com/tests.html', framework: 'mocha', platforms: [ ['Windows 7', 'firefox', '27'], ['Linux', 'chrome', 'latest'] ] }}, (err, response) => { if (err) { console.error(err); } else { console.log(response.body); }}); 你注意到body中的framework: 'mocha'。Sauce Labs供应商支持许多主流的JavaScript单元测试框架,包括 Mocha,Jasmine,Qunit和YUI。“支持”意味着Sauce Labs的webdriver客户端知道去哪获取测试结果。 如果你没有使用上面提到的测试框架,你能可以通过设置framework: 'custom',Sauce Labs将会代替找到的全局变量window.global_test_results。格式化的结果被列在文档中的自定义框架一节中。 让Mocha测试结果对于Sauce Labs的webdriver客户端有用尽管你在最初的请求中告诉Sauce Labs你在使用Mocha,你仍然需要去更新你的HTML页面,去存储Sauce Labs能访问的全局变量的测试结果。 为你的HTML页面增加Mocha支持: 123456<script> mocha.setup('bdd'); window.onload = function() { mocha.run(); };</script> 做一些事情,像下面这样: 1234567891011121314151617181920212223242526272829303132<script> mocha.setup('bdd'); window.onload = function() { var runner = mocha.run(); var failedTests = []; runner.on('end', function() { window.mochaResults = runner.stats; window.mochaResults.reports = failedTests; }); runner.on('fail', logFailure); function logFailure(test, err){ var flattenTitles = function(test){ var titles = []; while (test.parent.title){ titles.push(test.parent.title); test = test.parent; } return titles.reverse(); }; failedTests.push({ name: test.title, result: false, message: err.message, stack: err.stack, titles: flattenTitles(test) }); }; };</script> 在上面的代码和默认的Mocha模板中唯一不同的是分配给测试结果的变量名,就像Sauce Labs期望的格式一样叫做window.mochaResults。因为这个新的代码不会影响正在浏览器中运行的手工测试,你不妨就开始使用它作为默认的Mocha模板。 再次强调一点,当Sauce Labs“运行”你的测试时,它并没有做任何事,它只是单纯的访问一个页面,等到发现一个window.mochaResults对象,然后记录下这个结果。 确定你的测试通过还是失败StartJS Unit Tests 方法会告诉Sauce Labs去挨个在你指定的浏览器/平台运行测试,但是它不会返回测试的结果。 它返回所有工作队列中的ID,响应看起来像是这样的: 12345678{ "js tests": [ "9b6a2d7e6c8d4fd2afeeb0ff7e54e694", "d38688ec7256497da6966f4523ddee76", "14054e68ccd344c0bed77a798a9ce1e8", "dbc54181f7d947458f52201ea5fcb901" ]} 要确定你测试通过还是失败,你要调用GetJS Unit Status方法,它接手一个工作队列并且返回当前每个工作的工作状态。 这个想法是你要定期调用这个方法,知道所有工作都完成。 12345678910111213141516request({ url: `https://saucelabs.com/rest/v1/${username}/js-tests/status`, method: 'POST', auth: { username: process.env.SAUCE_USERNAME, password: process.env.SAUCE_ACCESS_KEY }, json: true, body: jsTests, // The response.body from the first API call.}, (err, response) => { if (err) { console.error(err); } else { console.log(response.body); }}); 响应的结果看起来像是这样: 1234567891011121314151617181920212223242526{ "completed": false, "js tests": [ { "url": "https://saucelabs.com/jobs/75ac4cadb85e415fae957f7811d778b8", "platform": [ "Windows 10", "chrome", "latest" ], "result": { "passes": 29, "tests": 30, "end": {}, "suites": 7, "reports": [], "start": {}, "duration": 97, "failures": 0, "pending": 1 }, "id": "1f74a237d5ba4a47b5a42570ae1e7999", "job_id": "75ac4cadb85e415fae957f7811d778b8" }, // ... the rest of the jobs ]} 一旦response.body.complete属性值为true,就表示你的测试已经运行完成,然后你就可以通过检查每个工作流程的通过还是失败。 本地访问测试我已经解释过Sauce Labs“运行”你的测试通过访问一个URL。当然,这意味着这个URL必须是公开在网络上可访问的链接。 有一个问题就是如果你的测试服务启动在localhost。 有很多解决这个问题的方案,包括Sauce Connect(官方推荐的一种),这是一个由Sauce Labs创建的代理服务器,在Sauce Labs虚拟机和本地主机之间开启一个安全连接。 Sauce Labs是处于安全性的考虑被设计的,并且使得外部无法获得你的代码。它的缺点就是十分复杂的设置与使用。 如果你的代码涉及到安全性,它可能值得你去弄清楚Sauce Labs;如果不是的话,有许多相似的方案去更简单的解决这个问题。 我选择的方案是ngrok ngrokngrok是一个用于创建安全隧道连接工具。它给你一个公共的URL到web服务器运行在你的本地机器上,确切的是你需要运行测试在Sauce Labs上。 如果你在虚拟机上进行开发或手动测试,你可能已经听说过ngrok,如果没有,那你应该去查阅一下了,它是极其有用的工具。 在你的机器上安装ngrok像是下载二进制文件,然后添加到你的路径中一样简单;如果你将会在Node中使用ngrok,你也需要通过npm安装它。 npm install ngrok 你可以用下面的代码以编程方式从Node中开始ngrok进程: 123456789const ngrok = require('ngrok');ngrok.connect(port, (err, url) => { if (err) { console.error(err); } else { console.log(`Tests now accessible at: ${url}`); }}); 只要你有一个公共的URL能访问你的测试文件,用Sauce Labs跨浏览器测试你的本地代码会变得十分容易。 整合碎片化这篇文章包含了很多主题,给人的印象是自动化的,跨浏览器的JavaScript单元测试是复杂的。但情况并非如此。 我从我的角度来看这篇文章-当我试图去解决这个问题。然后回顾我之前的经验,真正复杂的是缺少解决整个流程如何工作的有效信息,和怎么样把所有的整合到一起。 一旦你了解了所有的步骤,它很简单。总结: 最初的手工流程 写一个测试然后创建一个单一的HTML页面去运行它。 在本地的一个或者两个浏览器中运行这个测试,确保它能工作。 增加自动化流程 创建一个开源的Sauce Labs账号,获得一个用户名和访问权限。 更新你的测试页面源码,以便Sauce Labs能通过JavaScript全局变量读取测试结果。 用ngrok给你的本地测试页面创建一个安全隧道,这样就能在互联网公开的访问了。 调用StartJS Unit Tests接口方法列出你想测试的浏览器/平台。 定时调用GetJS Unit Test Status方法知道工作完成。 报告结果。 使测试变得更容易我知道这篇文章开头我谈了很多关于你不需要一个框架来做自动化,跨浏览器的JavaScript单元测试,我现在仍然坚信这个。然而,尽管每一步都很简单,你可能不想在每次都为项目编写代码。 我想给我的很多老项目增加自动化测试,所以对我来说打包这些逻辑到我的模块中是很有意义的。 我推荐你尝试实现一个你自己的框架,这样你就可以完全理解它是如何工作的,但如果你没有时间并且还想快速建立一个测试,我建议你使用我创建的库Easy Sauce。 Easy SauceEasy Sauce是一个Node包和一个命令行工具,现在我为我想做跨浏览器测试的每一个JavaScript项目都使用这个包。 easy-sauce 命令可以设置你的HTML测试文件的路径(默认是/test/)、开启本地服务的端口(默认是1337端口)和一系列的浏览器/平台进行测试。easy-sauce将会在Sauce Lab’s selenium cloud运行你的测试,将日志打印在控制台并通过合适的状态码告知你测试是否通过。 npm包使它变得更方便,easy-sauce将会默认在package.json文件中查找配置选项,所以你不必分别的存储它们。好处是用户更加明确的知道你的包支持浏览器/平台。 对于easy sauce完整的用法介绍,请查看Github文档。 最后,我想强调的是我专门建立这个项目来解决我的需要。虽然我认为这个项目对于很多开发人员都十分有用,但我没有计划把它变成一个功能齐全的测试解决方案。 结语在这篇文章的开始,我写下了一系列的需求。在Easy Sauce的帮助下,我正努力的在任何项目中满足这些需求。 如果你还没有为你的项目做自动化、跨浏览器的JavaScript单元测试,我鼓励你给Easy Sauce一个尝试的机会。即使你不想用Easy Sauce,你至少应该了解你自己的需求或更好地了解现有的工具。 Happy testing! 如果你能看到这里,很感谢你的耐心阅读。这是我翻译的第一篇技术文档,自身水平有限,所以翻译总有不当与疏漏,如有发现还请您耐心评论指出。]]></content>
<tags>
<tag>翻译</tag>
</tags>
</entry>
<entry>
<title><![CDATA[2016-10-23,写于1024之前...]]></title>
<url>%2F2016%2F10%2F23%2F2016-10-23-%E5%86%99%E4%BA%8E1024%E4%B9%8B%E5%89%8D%2F</url>
<content type="text"><![CDATA[其实一直有打算自己搭建一个博客的,但是犹豫种种的借口以及毕业之后拖延症病发,所以迟迟没有动手去做这件事。最近,越来越觉得不应该这样下去啦。就像五月天的歌词:我知道潮落之后一定有潮起,有什么了不起,现在“潮落”,也应该开始“潮起”了。 搭建博客的目的 现在能够写作/发帖的网站实在太多了,既然不能做到兼顾,那还不如自己精心运营一个自己的博客 将自己每天的所学所得,有一个分享的“部落”。而不是向之前一样,用马克飞象同步到自己的印象笔记中 作为一个web前端开发人员,能够将自己日常所学新技巧,可以“捣鼓”到自己的博客上 Just for fun!!! 1Hello world, again ! 明天10-24, 祝所有的程序猿“No-Bug-Day”。最近,会把自己在“简书”,“博客园”上面放的文章迁移过来。 Night-night,Yeaseon.]]></content>
<tags>
<tag>随笔</tag>
</tags>
</entry>
</search>