1、求数组最大值、最小值1
2const arrayMax = arr => Math.max(...arr);
//arrayMax([10,1,5]) 返回10
1 | const arrayMin = arr => Math.min(...arr); |
不废话,先看代码:
1 | <div id="example"> |
上面双大括号内是可以写表达式的,但是一般会写一些简单的表达式。太复杂的话,可读性比较差,难以维护。
所以,vue提供了计算属性这个东西,即我们所说的computed。
特点:计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算(这点详细作用可以对比文中第三点)
首先要知道计算属性里用来完成各种复杂逻辑(运算、函数调用等),只要最终返回一个结果即可。computed里有getter和setter,getter用来读取,当手动修改属性值时触发setter。
讲的啥,我也不明白。。。那我们看例子吧:1
2
3
4<div id="example">
<p>{{ reversedMessage }}</p>
<p>{{splitMessage}}</p>
</div>
1 | var vm = new Vue({ |
在此,只要会用默认getter,问题就不大。
在表达式中调用methods中的方法,同样可以达到同样的效果:1
<p>Reversed message: "{{ reversedMessage() }}"</p>
1 | // 在组件中 |
那我们直接用methods就好了呗,vue为何还要创建computed呢?问得好!
因为methods这种方法,值是实时更新的,当页面组件刷新或者执行this.message = 'world'等等,反正只要重新渲染,函数就行重新被执行。
然鹅,computed只有在依赖的实例数据改变时,才会更新(即所说的缓存)。那即使重新渲染,只要this.message的值不发生变化,computed里面getter就不会重新执行。所以当处理大量数据的时候,使用计算属性,而不是方法,这样会提高性能。
当this.message发生变化时候,触发setter、getter。好似监听、于是我们想到了watch。那他们的区别在哪呢?
例子:1
2
3
4
5<div id="myDiv">
<input type="text" v-model="firstName">
<input type="text" v-model="lastName">
<input type="text" v-model="fullName">
</div>
computed:1
2
3
4
5
6
7
8
9
10
11
12
13//这里不用再data中声明fullName
new Vue({
el:"#myDiv",
data:{
firstName:"Foo",
lastName:"Bar",
},
computed:{
fullName:function(){
return this.firstName + " " +this.lastName;
}
}
})
watch:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16new Vue({
el: '#myDiv',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
从上面我们可以总结出:
计算所得出最后的值。watch是去监听一个值的变化,然后执行相对应的函数。下一次获取computed的值时才会重新调用对应的getter来计算。watch在每次监听的值变化时,都会执行回调。其实从这一点来看,都是在依赖的值变化之后,去执行回调。很多功能本来就很多属性都可以用,只不过有更适合的。如果一个值依赖多个属性(多对一),用computed肯定是更加方便的。如果一个值变化后会引起一系列操作,或者一个值变化会引起一系列值的变化(一对多),用watch更加方便一些。新旧值,通过这两个值可以做一些特定的操作。computed通常就是简单的计算。说了这么多,其实我们可以简单理解为:
1、当我们想的值是实时更新的,我们用methods
2、当我们想要计算后的属性值,依赖于其他数据变化才更新,我们用computed
2、当依赖数据发生变化后,我们还要做其它的一些事情,我们用watch
单例模式保证类只有一个实例,并提供一个访问她的全局访问点
1 | function getSingle(fn){ |
解决一个问题的多个方法,将每种方法独立封装起来,相互可以转换
一个基于策略模式的程序至少由两部分组成,一个是一组策略类,策略类封装了具体的算法,并负责具体的计算过程,一个是环境类,环境类接受客户的请求,随后把请求委托给某个策略类
策略模式的一个使用场景:表单验证,将不同验证规则封装成一组策略,避免了多重条件判断语句
一句经典的话: 在函数作为一等对象的语言中,策略模式是隐性的,策略就是值为函数的变量
例子:1
2
3
4
5
6
7
8
9
10
11
12
13const S = (salary)=>{
return salary * 4
}
const A = (salary)=>{
return salary * 3
}
const B = (salary)=>{
return salary * 2
}
const calculate = (fun,salary)=>{
return fun(salary)
}
calculate(S,1000)
代理模式为一个对象提供一个代用品或占位符,以便控制对它的访问
不直接和本体进行交互,而是在中间加入一层代理,代理来处理一些不需要本体做的操作1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var myImage=function(){
var imgNode=document.createElement('img')
document.body.appendChild(imgNode)
return {
setImg(src){
imgNode.src=src
}
}
}
var proxyImg=function(){
var img =new Image()
img.onload=function(){
myImage.setSrc(this.src)
}
return {
setImg(src){
myImage.setSrc(‘loading.png’)
img.src=src
}
}
}
代理的意义
对单一职责原则的一种表现,单一职责原则指的是,一个函数或类应该只负责一件事,如何一个函数职责太多,等于把这些职责耦合在了一起,当一部分需要改动时,很有可能就影响到了函数的其他部分
观察者和发布、订阅模式使程序的两部分不必紧密耦合在一起,而是通过通知的方式来通信
一个对象维持一系列依赖于它的对象,当对象状态发生改变时主动通知这些依赖对象
这里注意是对象直接管理着依赖列表,这点也是观察者模式和发布、订阅模式的主要区别1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Subject{
constructor(){
this.observers=[]
}
add(observer){
this.observers.push(observer)
}
notify(data){
for(let observer of this.observers){
observer.update(data)
}
}
}
class Observer{
update(){
}
}
1 | class Pubsub{ |
命令模式的命令指的是一个执行某些特定事情的指令
命令模式最常见的使用场景是:有时候需要向某些对象发送请求,但是不知道请求的接受者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,是使得请求发送者和接受者消除彼此之间的耦合关系
命令模式的由来,其实是回调函数的一个面向对象的替代品
一句话来说,命令模式就是用一个函数来包裹一个具体的实现,这个函数统一定义了一个 execute 方法来调用具体的实现方法,而请求者只要和这个命令函数交流就行
享元模式顾名思义,共享一些单元,用于优化重复、缓慢及数据共享效率较低的代码
应用:一是用于数据层,处理内存中保存的大量相似对象的共享数据,二是用于 DOM 层,事件代理
在享元模式中,有个有关两个状态的概念 - 内部和外部
内部状态存储于对象内部,可以被一些对象共享,独立于具体的场景,通常不会变
外部状态根据场景而变化
剥离了外部状态的对象成为共享对象,外部状态在必要时被传入共享对象来组成一个完整的对象
使用享元模式的几个步骤:
以书中文件上传的例子描述
1.剥离外部状态1
2
3
4
5
6
7
8
9
10
11
12
13class Upload {
constructor(type) {
this.uploadType = type
}
delFile(id) {
uploadManager.setExternalState(id, this) //这里就是组装外部状态来使共享对象变成一个具体的对象
if (this.fileSize < 3000) {
//直接删除
return
}
//弹窗询问确认删除?
}
}
2.使用工厂进行对象实例化1
2
3
4
5
6
7
8
9
10
11var UploadFactory = (function() {
const flyWeightObjs = {}
return {
create(uploadType) {
if (flyWeightObjs[uploadType]) {
return flyWeightObjs[uploadType]
}
return flyWeightObjs[uploadType] = new Upload(uoloadType)
}
}
})()
3.使用管理器封装外部状态1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23var uploadManager = (function() {
var uploadDatabase = {}
return {
add(id, uploadType, fileSize, fileName) {
var flyWeightObj = UploadFactory.create(uploadType) //那个被共享的对象
//创建结点...
//删除操作
dom.onclick = function() {
flyWeightObj.delFile(id) //这个共享在步骤1中会被组合,可以看到,只有在删除操作的时候,我们才需要那些外部状态
}
uploadDatabase[id] = {
fileName,
fileSize,
dom
}
return flyWeightObj
},
setExternalState(id, flyWeight) {
var externalState = uploadDatabase[id]
Object.assign(flyWeight, externalState)
}
}
})()
将一个请求以此传递给多个函数,若请求符合当前函数要求,则当前函数处理,否则,传给下一个
很好很强大
责任链模式可以很好的避免大量的 if,else if,else1
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
45if (Function.prototype.chainAfter) {
throw new Error('the chainAfter method already exist')
} else {
Function.prototype.chainAfter = function(fn) {
return (...args) => {
const ret = this.apply(this, [...args, () => {
return fn && fn.apply(this, args)
}])
if (ret === 'NEXT') {
return fn && fn.apply(this, args)
}
return ret
}
}
}
/*
* example
* class Test{
*
* test(...args){
* alert('test')
* return 'NEXT'
* }
*
* test1(...args){
*
* setTimeout(()=>{
* alert('test1')
* args.pop()()
* })
* }
*
* test2(...args){
* alert('test2')
* }
*
* $onInit(){
* const chain = this.test.bind(this)
* .chainAfter(this.test1.bind(this))
* .chainAfter(this.test2.bind(this))
* chain(1,2,3)
* }
* }
*
*/
在不改变原有函数或对象功能的基础上,给它们新加功能
用 AOP 装饰函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21if (Function.prototype.before) {
throw new Error('the before method already exist')
} else {
Function.prototype.before = function(beforefn) {
return () => {
if (beforefn.apply(this, arguments)) {
this.apply(this, arguments)
}
}
}
}
if (Function.prototype.after) {
throw new Error('the after method already exist')
} else {
Function.prototype.after = function(afterfn) {
return () => {
this.apply(this, arguments)
afterfn.apply(this, arguments)
}
}
}
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类
要点:将状态封装成独立的函数,并将请求委托给 当前的状态对象,当对象的内部状态改变时,会带来不同的行为变化
电灯的例子:
一个按钮控制电灯的开关,按一下是开,再按一下是关
初始实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Light {
constructor() {
this.state = 'off',
this.button = null
}
init() {
//创建按钮结点
.....
this.button.onClick = () => {
this.btnPressed()
}
}
btnPressed() {
if (this.state == 'off') {
this.state = 'on'
} else {
this.state = 'off'
}
}
}
这段代码的缺点就是不易扩展,当要加入一种闪动的状态时,就要修改 btnPressed 中的代码
使用状态模式改写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
27class Light {
constructor() {
this.state = FSM.off,
this.button = null
}
init() {
//创建按钮结点
.....
this.button.onClick = () => {
this.state.btnPressed.call(this)
}
}
}
const FSM = {
on: {
btnPressed() {
//处理
this.state = FMS.on
}
},
off: {
btnPressed() {
//处理
this.state = FMS.off
}
}
}
ES6项目构建
好用的一些功能
1 | 1、数组去重 |
基本知识点
let声明1
2
3
4
5
6{
let a=10;//let声明的变量只在它所在的代码块有效
var b=1;
}
console.log(b);
// console.log(a);//报错
let应用
1 | for(let i=0;i<10;i++){} |
let不支持变量提升
只要块级作用域内存在let命令,它所声明的变量就“绑定”了这块区域,不再受外部的影响
1 | var temp=123; |
外层作用域无法读取内层作用域的变量
1 | {{{{ |
内层作用域可以定义外层作用域的同名变量
1 | {{{{ |
作用:使的获得广泛应用的立即执行函数表达式(IIFE)不再必要了
1 | // IIFE 写法 |
块级作用域本质上是语句,没有返回值,所以在外部没法或许执行结果
可以通过do表达式获取块级作用于中的最后执行的表达式的值。
1 | let x = do{ |
const 声明一个只读的常量。一旦声明,常量的值就不能改变。—所以常量一旦声明就必须立即初始化,不能留到以后赋值
const 的作用域与let命令相同:只在声明所在的块级作用域内有效
const 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
const实际保证的,并不是变量的值不得改动,而是变量指向的哪个地址不得改动
如:const foo={};
foo存的是地址,这个地址指向一个对象,这个对象本身是可变的,故可添加属性foo.prop=123;
但是不可把foo指向另一个地址:foo = {}//错误
————-让对象冻结的方法(不可改变属性)—————1
2const foo=Object freeze({});
foo.prop=123;//常规模式下,此行代码不起作用。 严格模式下会报错
————-让对象彻底冻结的方法————————-
注:对象的属性有可能还是对象,那么对象的属性就有可能有自己的属性,那么对象的属性也要被冻结
***使用回调函数
1 | var constantize = (obj)=>{ |
var function let const import class es5只有前两种
顶层对象:window(浏览器环境),global(Node环境)
window.a=1; 和 a=1; 是一样的,即顶层对象属性和全局变量是一样的
这是JavaScript设计的败笔之一,ES6为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从ES6开始,全局变量将逐步与顶层对象的属性脱钩。
1 | var a = 1; |
有一个提案,在语言标准的层面,引入global作为顶层对象。也就是说,在所有环境下,global都是存在的,都可以从它拿到顶层对象
垫片库(system.global)模拟了这个提案,可以在所有环境中拿到global
1 | import getGlobal from 'system.global'; |
1 | let [foo, [[bar], baz]] = [1, [[2], 3]]; |
如果解构不成功,变量的值就等于undefined。
1 | let [foo] = []; |
以上两种情况都属于解构不成功,foo的值都会等于undefined。
不完全解构:
1 | let [x, y] = [1, 2, 3];// x 为1, y为2 |
//下面情况将会报错,右边必须为数组(可遍历的结构——Iterator 接口)形式
1 | let [foo] = 1; |
对于 Set 结构,也可以使用数组的解构赋值。
1 | let [x, y, z] = new Set(['a', 'b', 'c']); |
事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
1 | function* fibs() { |
解构赋值允许指定默认值
1 | let [foo = true] = []; |
ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。1
2
3
4let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined。
==惰性求值==:即只有在用到的时候才会求值
默认值可以引用解构赋值的其他变量,但该变量必须已经声明1
2
3let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError
数组的解构赋值是有顺序的,但是对象的解构赋值是无需的,变量名和属性同名才能获取到正确的值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let { bar: bar, foo: foo } = { foo: "aaa", bar: "bbb"};
// 可以简写为
let { bar, foo } = { foo: "aaa", bar: "bbb"};
foo // "aaa"
bar // "bbb"
let {foo: xiao , baz } = { foo: "aaa", bar: "bbb"};
baz // undefined
xiao // "aaa"
//真正被赋值的是后者
foo // error : foo is not defined
let obj = { first: 'hello', last: 'world' };
let { first: f, last: 1 } = obj;
f // 'hello'
l // 'world'
与数组一样,结构也可以用于嵌套结构的对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"
//如果写法如下:那么p既是模式又是变量
let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]
一个嵌套的例子:
1 | let obj = {}; |
对象的结构也可以设置默认值,默认值生效的条件是严格等于undefined
如果结构失败,变量的值等于undefined
1 | var { x = 1, y = 2 } = { x: undefined, y: null }; |
对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量1
2
3let {sin, cos, tan} = Math;
Math.sin // 一个sin function
sin // 一个sin function 直接将Math中的方法给了sin
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象1
2
3
4
5
6
7const [a, b, c, d, e] = 'hello';
a // "h"
e // "o"
//类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值
let {length : len} = 'hello';
len // 5
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象,无法转化成对象,将会报错1
2
3
4
5
6
7
8let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
1 | function add([x, y]){ |
undefined就会触发函数参数的默认值。1
2[1, undefined, 3].map((x = 'yes') => x);
// [ 1, 'yes', 3 ]
建议只要有可能,就不要在模式中放置圆括号
1.变量声明语句1
2
3
4
5
6
7//全部报错
let [(a)] = [1];
let {x: (c)} = {};
let ({x: c}) = {};
let {(x: c)} = {};
let {(x): c} = {};
let { o: ({ p: p }) } = { o: { p: 2 } };
2.函数参数1
2
3
4// 报错
function f([(z)]) { return z; }
// 报错
function f([z,(x)]) { return x; }
3.赋值语句模式1
2
3
4
5
6// 全部报错
({ p: a }) = { p: 42 };
([a]) = [5];
// 报错
[({ p: a }), { x: c }] = [{}, {}];
只有一种:赋值语句的非模式部分,可以使用圆括号1
2
3[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确
1.交换变量的值1
2
3let x = 1;
let y =2;
[x, y] = [y, x];
2.从函数返回多个值
直接取值就可以1
2
3
4
5
6
7function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
3.函数参数的定义
4.提取JSON数据1
2
3
4
5
6
7
8let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
5.函数参数的默认值1
2
3
4
5
6
7
8
9
10
11jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
};
指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || ‘default foo’;这样的语句
6.遍历Map结构1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
//如果只想获取键名,或者只想获取键值,可以写成下面这样。
// 获取键名
for (let [key] of map) {
// ...
}
// 获取键值
for (let [,value] of map) {
// ...
}
7.输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。1
const { SourceMapConsumer, SourceNode } = require("source-map");
es6为字符串添加了遍历器接口,而且增加的for…of循环识别码点1
2
3
4
5
6
7
8
9
10
11let text = String.fromCodePoint(0x20BB7);
for (let i of text) {
console.log(i);
}
// "𠮷"
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
es5中的charAt方法可以返回字符串指定位置的字符,但是不识别码点大于0xFFFF的字符。es6提供了at()方法,可以实现这一要求1
2
3
4'abc'.charAt(0) //"a"
'𠮷'.charAt(0) // "\uD842"
'abc'.at(0) // "a"
'𠮷'.at(0) // "𠮷"
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。1
2
3
4
5
6
7
8
9
10let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
//这三个方法都支持第二个参数,表示开始搜索的位置。
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
repeat方法返回一个新字符串,表示将原字符串重复n次。1
'hello'.repeat(2) // "hellohello"
字符串补全长度的功能9
vue.js 轻量级的MVVM框架
vue介绍:
v-if是条件渲染指令,它根据表达式的真假来删除和插入元素,它的基本语法如下:
v-if=”expression”
v-show根据表达式的真假来删除和插入元素
v-if=”expression”
可以用v-else指令为v-if或v-show添加一个“else块”。
v-else元素必须立即跟在v-if或v-show元素的后面——否则它不能被识别。
v-if和v-else连用时,不执行else时,else里面的HTML不渲染
v-if和v-show连用时,不执行else时,else里面的HTML渲染,只是display:none隐藏了
v-for=”item in items” items是一个数组,item是当前被遍历的数组元素。
总是key配合v-for使用1
2
3
4
5<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
v-bind 指令可以在其名称后面带一个参数,中间放一个冒号隔开,这个参数通常是HTML元素的
特性(attribute–专门用来绑定属性),例如:v-bind:width=""v-bind:argument="expression"
简写形式如下::argument="expression"
另外可以绑定类名
用法1:
:class="[className1,className2,className3]"其中,className1、2、3是数据 放在data中的
用法2:
:class="{className1:true,className2:false}"其中,className1、2是真正的类名
用法3:
:class="json" json是data里面的json
可以绑定style
:style="[c]" c是json形式的数据
:style="[c,d]"c和d都是json形式的数据,多个样式的写法
:style="json" json是data中的数据
v-on指令用于给监听DOM事件,它的用语法和v-bind是类似的,例如监听<a>元素的点击事件:<a v-on:click="doSomething">
v-on 有简写形式,如下:<a @click="doSomething">
1 | {{*msg}} |
数据只绑定一次1
{{{msg}}}
将HTML转译输出–>v-html=”msg” 后面的方法可以防止闪烁,前面的方法v2.0已经删掉了
过滤模板数据 系统提供一些过滤器:1
2{{msg| filterA}}
{{msg| filterA | filterB}}
注:过滤器主要是按照自己写的方法筛选数据
一般定义的方法都放在methods中,进行调用