0%

宏任务微任务

  1. 宏任务包括 setTimeout setInterval postMessage
  2. 微任务包括 promise.then process.nextTick(node)
    执行一个宏任务(栈中没有就从事件队列中获取)
    执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
    宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
    当前宏任务执行完毕,开始检查渲染,然后 GUI 线程接管渲染
    渲染完毕后,JS 线程继续接管,开始下一个宏任务(从事件队列中获取)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//打印  promise start  promise.then setTimeout
{
setTimeout(() => {
console.log("setTimeout");
}, 0);
new Promise((resovle, reject) => {
console.log("promise");
resovle();
}).then(res => {
console.log("promise then");
});

console.log("start");
}

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
{
// pr1 2 then1 set1 then2 then4 set2
setTimeout(function() {
console.log("set1");
new Promise(resovle => {
resovle();
}).then(function() {
new Promise(function(resovle) {
resovle();
}).then(function() {
console.log("then4");
});
console.log("then2");
});
});
new Promise(function(resovle) {
console.log("pr1");
resovle();
}).then(function() {
console.log("then1");
});
setTimeout(function() {
console.log("set2");
});
console.log(2);
}


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
{
//1.对象的赋值就是对象的引用地址赋值
/*
解题步骤:
1. a的地址假设是1000 a赋值给b b指向同样的地址
a -- 1000 {n:1}
b -- 1000 {n:1}
2. 运算符优先级 . 更高
a.x 在1000这个地址上开辟一个x
{n:1,x:} -- 1000

a={n:2} a 指向了一个新的地址 1002
a -- 1002 {n:2}
再赋值给x
{n:1,x:{n:2}} -- 1000
此时 a已经指向1002 b 还是指向1000
a -- 1002 {n:2}
b --1000 {n:1,x:{n:2}}
*/

var a = { n: 1 };
var b = a;
a.x = a = { n: 2 };
console.log("a", a); //{n:2}
console.log("b", b); //{n:1,x:{n:2}}
}

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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296

// 参数解构
function demo({ first, second }: { first: number; second: number }) {
return first + second;
}

console.log(demo({ first: 1, second: 2 }));

//数组
const arr: (number | string)[] = [1, 2, "3"];

type User = {
name: string;
age: number;
};
const objArr: User[] = [
{
name: "dell",
age: 18
}
];

//type 指定传参可以是函数
type fn = () => string
function bbb(n:string|fn ) {
return 1
}

bbb(function(){return ''})

//元组 tuple 数组中个数,和每个类型是固定的
const teacherInfo: [string, string, number] = ["dell", "male", 18];

const teacherList: [string, string, number][] = [
["dell", "male", 18],
["boby", "male", 33],
["jenny", "female", 39]
];

//interface 和 type类型别名的区别是 type也可以表达基础数据类型
//比如:type person1 = string
interface Person {
name: string;
age?: number;
[propName: string]: any; //今后可能会有不可预知的属性 只要KEY是STRING,值是ANY就可以
//如果不写 下边我传sex会报错
say(): string;
}

const getName = (person: Person) => {
return person.name;
};

const setName = (person: Person, name: string): void => {
person.name = name;
};

let na = getName({
name: "weibin",
age: 18,
sex: "male", //如果接口没有定义 [propName: string]: any; 会报错 因为未特殊指明有sex
say() {
return "bark";
}
});

console.log(na);

//类应用接口 implements是对接口的实现 作用就是interface约定好接口的内容 各端自己实现
class somebody implements Person {
name = "dell";
say() {
return "hello";
}
}

//接口继承
interface Teacher extends Person {
teach(): void;
}

//定义函数类型的接口
interface SayHi {
(word: string): string;
}

const say: SayHi = (word: string) => {
return word;
};

//单列

class Demo {
private static instance: Demo;
//public name: string 这个参数的写法相当于构造函数写了 this.name = name;
private constructor(public name: string) {}
//static 可以方法直接挂在到类上 而不是类的实例上
static getInstance(name: string) {
if (!this.instance) {
this.instance = new Demo(name);
}
return this.instance;
}
}

//let demo1 = new Demo() //报错 因为先执行构造函数 private 外部无法执行

const demo1 = Demo.getInstance("weibin");
console.log(demo1.name);



//联合类型
interface Bird {
fly: Boolean;
sing: () => {};
}

interface Dog {
fly: Boolean;
bark: () => {};
}

//animal 是无法知道你要调用的有sing方法还是bark方法 只能通过断言
//或者判断写 if('sing' in animal)
function trainAnimal(animal: Bird | Dog) {
if (animal.fly) {
(animal as Bird).sing();
} else {
(animal as Dog).bark();
}
}

//用typeof形式做类型保护
function add(first: string | number, second: string | number) {
if (typeof first === "string" || typeof second === "string") {
return `${first}${second}`;
}
return first + second;
}

//instanceof
class NumberObj {
count = 10;
}

function addSecond(first: object | NumberObj, second: object | NumberObj) {
if (first instanceof NumberObj && second instanceof NumberObj) {
return first.count + second.count;
}
}

//枚举类型 enum

enum Status {
Offline = 2,
Online,
error
}
console.log(Status.Offline, Status.Online, Status.error); // 2,3,4 默认 0,1,2
console.log(Status[2]); //Offline

//泛型 比如一个函数 传入的两个参数必须是一致的类型

function join<ABC>(first: ABC, second: ABC) {
return `${first}${second}`;
}
//join(1, "2"); //报错
join(1, 2);

//T 类型的数组
class DataManager<T> {
constructor(private data: T[]) {
// this.data = data;
}
getItem(index: number) {
console.log(this.data[index]);
}
}

const data = new DataManager(["1", "2"]);
data.getItem(1);

//泛型继承 泛型必须有一个name属性
//如果指定泛型必须是一个string 或者 number 也可以写成 T extends number | string
interface item {
name: string;
}

class DataManager2<T extends item> {
constructor(private data: T[]) {}
getItem(index: number) {
console.log(this.data[index].name);
}
}

const data2 = new DataManager2([
{
name: "weibin"
}
]);
data2.getItem(0);


//如果要需要返回参数的长度,又不能指定为数组 (T[])因为字符串也可以返回长度
//可以借助接口
interface IwithLength {
length: number
}

function echoWithLength<T extends IwithLength>(arg: T): T {
console.log(arg.length)
return arg
}

//内置类型
interface Ipserson {
name: string
age: number
}

//正常必须写2个属性
let a: Ipserson = { name: 'weibin', age: 10 }

//使用 Partial内置类型 可以让所有选项变为可选
type Ipartial = Partial<Ipserson>
let a1: Ipartial = { name: 'weibin' }


//如果有多个命名空间的引用
///<reference path="components.ts" />
//命名空间 就是把方法包裹起来不污染全局,如果内部哪个方法想暴露出去就用export

namespace Home {
export class Page {
constructor() {
new Components.Header();
}
}
}

//另一个命名空间 components.ts

// namespace Components {
// export class Header {
// constructor() {
// const elem = document.createElement("div");
// elem.innerText = "This is Header";
// document.body.appendChild(elem);
// }
// }
// }


//方法装饰器 首先装饰器需要打开tsconfig.json的配置 experimentalDecorators/emitDecoratorMetadata
//装饰器都是函数
//方法装饰器 如果是普通方法 target对应调用类的prototype
//如果是静态方法 target 对应的是类的构造函数
//key对应的是方法名
//descriptor.value对应的是方法本身
const user: any = undefined;
//用工厂方法可以穿参
function catchError(msg: string) {
return function(target: any, key: string, descriptor: PropertyDescriptor) {
//打印出 Test { getName: [Function], getAge: [Function] } 'getName' [Function]
console.log(target, key, descriptor.value);
//重写getName,getAge方法
//这里要重新制定下当前方法 要不下边直接用descriptor.value()会报错
const fn = descriptor.value;
descriptor.value = function() {
try {
fn();
} catch (e) {
console.log(msg);
}
};
};
}
class Test {
@catchError("userInfo.name 不存在")
getName() {
return user.name;
}
@catchError("userInfo.age 不存在")
getAge() {
try {
return new user.age();
} catch (e) {
console.log("user.age bu cun zai");
}
}
}

const test = new Test();
test.getName();

tsconfig.json 配置

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
{
/* 只编译当前目录下的 demo.ts文件 如果没有写这个 include */
/*那么tsc命令就是编译根目录下所有的TS文件(如果tsc后面跟具体文件名就不会应用这个tsconfig.json文件)*/
//"include": ["./demo.ts"],
// "exclude": [],
"compilerOptions": {
// "incremental": true, /* 增量编译,之前编译过的不会编译 会生成一个文件记录编译信息*/
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
// "lib": [], /* Specify library files to be included in the compilation. */
"allowJs": true /* 允许JS文件被编译 */,
"checkJs": true /* 检测JS报错 */,
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
//"outFile": "./build/page.js" /* 输出打包到一个文件.如果指定的话就不支持commonjs格式 一般把module改为 AMD */,
"outDir": "./build" /* 编译后的输出目录 */,
"rootDir": "./" /* 指定要编译的目录 */,
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
"removeComments": true /* 删除注释. */,
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": true /* null 是否可以作为赋值给其他基础类型 */,
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */

/* Additional Checks */
// "noUnusedLocals": true /* 定义的变量没有被使用报错 */,
//"noUnusedParameters": true /* 定义的参数没有被使用报错 */,
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */

/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* 制定根路径 */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */

/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */

/* Advanced Options */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}


为什么Vue3.0不再使用defineProperty实现数据监听?

链接地址

简易使用defineProperty实现双向绑定

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
 <div id="app"></div>

function vue() {
this.$data = {a:1};
this.$el = document.getElementById('app')
this.observe(this.$data)
this.render(); //初始页面中打印出1
}

vue.prototype.observe = function(data) {
var value;
var self = this;
for (var key in data) {
value = data[key]

if(typeof value == 'object') {
this.observe(value)
}
else{
Object.defineProperty(this.$data,key,{
get(){
//此处省略依赖收集
return value;
},
set(newval) {
value = newval;
self.render() //修改this.$data.a后触发方法再次渲染到页面
}
})
}
}
}

vue.prototype.render = function() {
this.$el.innerHTML = this.$data.a
}

var vm = new vue()
setTimeout(()=>{
vm.$data.a = 4444444
},2000)



Proxy的优势

  1. 直接代理整个对象,不用for in 循环了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};

const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key === 'text') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
},
});

input.addEventListener('keyup', function(e) {
newObj.text = e.target.value;
});

  1. Proxy可以直接监听数组的变化

使用react-redux需要安装的插件

cnpm i -S redux react-redux react-thunk

page -> Action -> Store -> Reducer ->Store -> page

  • 根文件需要引用 Provider 来给全局组件挂载store
  • 业务组件使用需要connect导出组件和全局STORE做连接,业务组件通过mapStateToProps,mapDispatchToProps,可以直接取到STORE中的state和dispatch你的action
  • 各个业务线都可以建立自己的STORE文件夹,所有导出的reducer 通过 combinReducer在根路径下
    的store/reducer中联合使用
  • 建议谷歌安装插件 redux devtools 方便调试,本例已经使用
  • 需要请求接口或异步加载的内容 需要安装react-thunk使action可以导出异步函数

项目链接:
todoList
简书项目

相关代码:

  • provider
1
2
3
4
5
6
7
8
9
10
<Provider store={store}>
<div className="App">
<BrowserRouter>
<Header></Header>
<Route exact path='/' component={Home}></Route>
<Route exact path='/detail' component={Detail}></Route>
</BrowserRouter>
</div>
</Provider>

  • connect
    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
    //取得store中state的数据 挂到 this.props
    const mapStateToProps = (state) =>{
    return {
    list:state.todoList.todoList
    }
    }
    //向 store 派发action 触发 reducer 挂在到 this.props
    const mapDispatchToProps = (dispatch)=>{
    return {
    initList() {
    dispatch(initTodoAction())
    },

    addTodo(data,input) {
    input.value = ''
    dispatch(addTodoAction(data))

    },
    deleteTodo(index) {
    dispatch(deleteTodoAction(index))
    }
    }
    }


    export default connect(mapStateToProps,mapDispatchToProps)(Home);

  • 根目录下store/reducer.js
1
2
3
4
5
6
7
8
9
10
11
12
import {combineReducers} from 'redux'

import todoListReducer from '../pages/home/store/reducer'
import addMinusReducer from '../pages/detail/store/reducer'

const Reducer = combineReducers({
todoList:todoListReducer,
addMinus:addMinusReducer
})

export default Reducer

MVVM

  1. M - model (数据模型,为 data 部分)
  2. v - view (视图层)
  3. vm - viewModel(ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,ViewModel 通常要实现一个 observer 观察者,当数据发生变化,ViewModel 能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel 也能监听到视图的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。。)

响应式原理

  1. Object.defineProperty 缺点:
  • 深度监听(复杂对象)需要递归,计算量大
  • 无法检测到新增,删除的属性 需要用 Vue.set/vue.delete
  • 无法监听数组方法,需要特殊处理 (尤雨溪 github 回答是因为 JS 性能原因)
  • 那么 vue 如何监听数组
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
// 获取数组的原型Array.prototype,上面有我们常用的数组方法
const oldArrayProperty = Array.prototype

// 创建一个空对象arrayMethods,并将arrayMethods的原型指向Array.prototype
const arrProto = Object.create(oldArrayProperty);

const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse'];

// 将上面的方法重写
methodsToPatch.forEach(function (method) {
arrProto[method] = function() {
updateView() //触发视图更新
oldArrayProperty[method].call(this, ...argruments) //调用原数组的方法
}
})

//这样 arrProto 上就有methodsToPatch这些方法 同时 arrProto.__proto__ 有原来Array.prototype的方法


function observer(target) {
if(typeof target !== 'object' || target ===null) {
return target
}

//如果是数组把数组的原型链指向重写过的数组原型
if(Array.isArray(target)) {
target.__proto__ = arrProto
}


for(let key in target) {
defineReactive(target,key,target[key]) //defineProperty
}

}



  1. vue3 用 proxy 有兼容性问题,无法用 polyfill

虚拟 DOM

概念: 用普通 js 对象来描述 DOM 结构

  1. 虚拟 DOM 结构
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
<div id="div1" class="container">
<ul style="font-size:20px">
<li>a</li>
</ul>
</div>



{
tag:'div',
props:{
id:"div1",
className:'container'
}
children:[
{
tag:'ul',
props:{
style:"font-size:20px"
},
children:[
{
tag:"li",
children:'a'
}
]
}
]
}

DIFF 算法

  • 只比较同一层级,不跨级比较

  • tag 不相同,则直接删掉重建,不在深度比较

  • tag 和 key,两者都相同,则认为是相同节点,不再深度比较 (为什么 v-for 中要使用 key)

  • h 函数就是 vue 中的 createElement 方法,这个函数作用就是创建虚拟 dom,追踪 dom 变化的。。。

1
2
3
4
5
const app = new Vue({
··· ···
render: h => h(App)
})

这个 render 方法也可以写成这样:

1
2
3
4
5
6
7
const app = new Vue({
··· ···
render:function(createElement){
return createElment(App)
}
})

vue 模板编译成什么

const template = '<p>{{message}}</p>'
with(this){return _c('p',[_v(_s(message))])}

  • with 语法括号内的 this 代表 vm 实例 _c(createElement),_v(createVnode) 都是绑定在 vm 实例上的方法

  • 模板编译为 render 函数,执行 render 函数返回 vnode

  • 使用 webpack vue-loader 会编译模板

  • vue 组件中使用 render 代替 template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Vue.component('heading',{
render:function(createElment){
return createElement(
'h1',
[
createElement('a',{
attrs:{
name:'headerId',
href:'#'
}
},'this is a tag')
]
)
}
})



前端路由

  1. hash
  • hash 变化会触发网页跳转,即浏览器的前进、后退
  • hash 变化不会刷新页面,SPA 必须的特点
  • hash 永远不会提交到 server 端(前端自生自灭)
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
<p>hash test</p>
<button id="btn1">修改 hash</button>

<script>
// hash 变化,包括:
// a. JS 修改 url
// b. 手动修改 url 的 hash
// c. 浏览器前进、后退
window.onhashchange = (event) => {
console.log('old url', event.oldURL)
console.log('new url', event.newURL)

console.log('hash:', location.hash)
}

// 页面初次加载,获取 hash
document.addEventListener('DOMContentLoaded', () => {
console.log('hash:', location.hash)
})

// JS 修改 url
document.getElementById('btn1').addEventListener('click', () => {
location.href = '#/user'
})
</script>

  1. H5 history
  • 用 url 规范的路由,但跳转时不刷新页面
  • history.pushState
  • window.onpopstate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<p>history API test</p>
<button id="btn1">修改 url</button>

<script>
// 页面初次加载,获取 path
document.addEventListener('DOMContentLoaded', () => {
console.log('load', location.pathname)
})

// 打开一个新的路由
// 【注意】用 pushState 方式,浏览器不会刷新页面
document.getElementById('btn1').addEventListener('click', () => {
const state = { name: 'page1' }
console.log('切换路由到', 'page1')
history.pushState(state, '', 'page1') // 重要!!
})

// 监听浏览器前进、后退
window.onpopstate = (event) => { // 重要!!
console.log('onpopstate', event.state, location.pathname)
}

apply

MSDN定义:

func.apply(thisArg, [argsArray])
参数节
thisArg
可选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
argsArray
可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。 浏览器兼容性 请参阅本文底部内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Test {
do1() {
var arg = Array.prototype.slice.call(arguments)
console.log(arg)
}
do2() {
this.do1.apply(this,arguments)
}
do3() {
this.do1(arguments)
}
}

var a = new Test()
a.do2(test) //[f]
a.do3(test) //[Arguments(f)]

a.do2(1) // [1]
a.do3(1) //[Arguments(1)]

function test() {

}

arguments 是一个对应于传递给函数的参数的类数组对象
还可以使用Array.from()方法或扩展运算符将参数转换为真实数组:

1
2
var args = Array.from(arguments);
var args = [...arguments];

以上代码如果执行DO3方法 不用apply 那么传入的无论是函数还是数字就不是一个单一的函数或者数字
会被arguments包一层或者用do4方法,将数组解构

使用nginx处理的问题

  1. 使用NODE起服务(GULP)页面要交给后端,那么静态资源都以’//asserts.xcarimg.com/resource/delayLoading.min.js`这种形式提交后端
    使用NGINX代理保证页面直接使用这种连接,交后端后不用修改

  2. 后端的接口存了cookie 到x c a r域名 ,我们本地如果用localhost是取不到的
    SO,使用NGINX代理URL到一个虚拟的 X C A R 域名

下载NGINX包 配置如下

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
### 为了指定asserts.xcar.com到本地,之后给后端的路径就不用变了 需要指定HOST  127.0.0.1 asserts.xcarimg.com
server {
listen 80;
server_name asserts.xcarimg.com;
root "D:/work/";
location / {
index index.html index.htm index.php;
if (!-e $request_filename){
rewrite ^/(.*) /index.php last;
}
#autoindex on;
}
}

###转发域名到dev1.xcar.com.cn 后端存COOKIE为了取COOKIE 需要指定HOST 127.0.0.1 dev1.xccar.com.cn 保证在相同域名下
server {
listen 80;
server_name dev1.xcar.com.cn;
root "D:/work/resource/index2019/dist/html/index/";
location / {
index index.html index.htm index.php;
if (!-e $request_filename){
rewrite ^/(.*) /index.php last;
}
#autoindex on;
}
}

1.查看原页面源代码,用editplus改成gb2312

2.打开fiddle add rule

3.拷贝一份你需要更改的文件(css,js,页面)到桌面

4,add rule后下边俩输入框 上边一个输入要拦截的线上地址(要修改的文件)

5,下边是你保存的文件(可更改) 点击SAVE 强刷页面看效果

6,如果是压缩的代码 去掉min到=号之间的

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
{
//replace 参数1:要被替换的字符(可以是正则表达式) 参数2:替换的字符
//将下面语句中的 is 注意不包含this中的is 替换成 IS
// \b代表单词边界 这里指单独的is
var str = 'He is a Boy,This is a Dog';
var reg = /\bis\b/g
console.log(str.replace(reg, 'IS')) //He IS a Boy,This IS a Dog

//用函数表达式 因为字符串的转义问题,字符串的两个\\实际上是一个\。
var reg1 = RegExp('\\bis\\b', 'g');
console.log(str.replace(reg1, 'IS')) //He IS a Boy,This IS a Dog

//非单词边界 比如我只想替换 this 中的 is
var reg2 = /\Bis\b/g
console.log(str.replace(reg2, 'IS')); //He is a Boy,ThIS is a Dog


}

{
//[]中括号代表或者 “|” 也可以
//匹配 年份-月份-日 或者 年份/月份/日
var reg = /^\d{4}[-/]\d{2}[-/]\d{2}$/
var regx = /^\d{4}-|\/\d{2}[-/]\d{2}$/
console.log(reg.test('2008-08-12')) //true
console.log('---------------', regx.test('2008-08-12')) //true
//匹配a或者b或者c忽略大小写
var reg2 = /[abcd]/gi
console.log('a1b2c3D4'.replace(reg2, '0')) //01020304

//字符取反 不是a或者b或c或d的 替换
var reg3 = /[^abcd]/gi
console.log('a1b2c3D4'.replace(reg3, 0)) //a0b0c0D0

//范围类 匹配大小写a-z 和 ‘-’
var reg4 = /[a-zA-Z-]/g
console.log('a1b2c3D4-'.replace(reg4, 0)) //010203040
}

{
//匹配一个ab+数字+任意字符
var reg = /ab\d./
console.log(reg.test('ab9999aaa')) //true

//贪婪模式 匹配尽量多
var reg2 = /\d{3,5}/g
console.log('123456789'.match(reg2)) //["12345", "6789"]

//非贪婪模式 量词后边加问号 match返回存放匹配结果的数组。该数组的内容依赖于 regexp 是否具有全局标志 g。
var reg3 = /\d{3,5}?/g
console.log('123456789'.match(reg2)) //["123", "456", "789"]

//分组

var reg4 = /Br(on|ca)sper/g
console.log('BronsperBrcasper'.replace(reg4, '0')); //00

//反向引用
//将 2015-02-21 修改成 02/21/2015 $1...n 可以捕获分组
//如果不想捕获 用 ?: 列(?:XXXXX)
var reg5 = /(\d{4})-(\d{2})-(\d{2})/g
console.log('2015-02-21'.replace(reg5, '$2/$3/$1')); //02/21/2015

var r = '2015-02-212015-03-21'.match(reg5);
console.log('11111111', r); //["2015-02-21", "2015-03-21"]



}

{
//断言
//匹配英文字符,他后边必须是个数字,但是数字不在匹配范围内
//?=\d后边是数字 ?!\d后边不是数字
var reg = /\w(?=\d)/g
var reg2 = /\w(?!\d)/g
console.log('a2bbc3'.match(reg)) //["a", "c"]
//console.log('a2bbc3a'.replace(reg2,'0')) //a200c3

//如匹配姓zhao,但名字不叫xianlie的人
var reg2 = /zhao(?!xianlie)/g

console.log('zhaook,zhaoxianlie,liuxianlie'.replace(reg2, 'wei'))
//weiok,zhaoxianlie,liuxianlie


//split 传入正则 匹配 , 或者 | 或者 *
console.log('a,b|c*d'.split(/,|\*|\|/g)) //["a", "b", "c", "d"]



//replace function 参数1:匹配到的字符串 参数2:分组内容,没有则忽略 参数3:匹配项的index 参数4:源字符串
let newStr = 'a1b2c3d4'.replace(/\d/g, function (match, index, origin) {
return parseInt(match) + 1
})

console.log(newStr) //a2b3c4d5



function getUrlParams(str) {
//如果 regexp 没有标志 g,那么 match() 方法就只能在 stringObject 中执行一次匹配。如果没有找到任何匹配的文本, match() 将返回 null。否则,它将返回一个数组,其中存放了与它找到的匹配文本有关的信息。该数组的第 0 个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本。除了这些常规的数组元素之外,返回的数组还含有两个对象属性。index 属性声明的是匹配文本的起始字符在 stringObject 中的位置,input 属性声明的是对 stringObject 的引用。

//(^|&)意思是从头开始匹配字符&, =([^&]*)意思是匹配=后面零个或多个不是&的字符,直至碰到第一个&为止,(&|$)意思就是匹配最后一个&,在正则表达式中,增加一个()代表着匹配数组中增加一个值, 因此代码中的正则匹配后数组中应包含4个值, 在getUrlParams("name")函数中,此时 r 获取到的数组应该是 ["name=elephant&", "", "elephant", "&"]

// 注意哦,,,如果正则没有 g 标志 match 返回的该数组的第 0 个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式(就是括号)匹配的文本。

var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); //定义正则表达式
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;

// return r && decodeURIComponent(r[2]);
}

window.location = "http://www.baidu.com?name=elephant&age=25&sex=male";
var name = getUrlParams("name"); //elephant
var age = getUrlParams("age"); //25
var sex = getUrlParams("sex"); //male
}

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
// 保存:删除关闭按钮
var reg = /<div\s+data\-del\=\"(\w+)\"[\s\S]+?<\/div>|<span\s+data\-del=\"(\w+)\"[^>]+><\/span>/g;
// 替换前:
// 图片:
`<div data-del="img" class="_editor_title _editor_img_title">
<span data-del="img" class="_editor_close"></span>
</div>`
// 视频
`<div data-del="video" class="_editor_title _editor_video_title">
<span data-del="video" class="_editor_title_btn" data-ind="1551928713346">添加封面图</span>
<span data-del="video" class="_editor_close"></span>
</div>`
// 替换后:
// 图片
`<!-- img del btn -->`
// 视频
`<!-- video del btn -->`

/////////////////////////////////////////////////////////////////////////

// 保存:图注
var reg = /<div[^>]*><input\s+class\=\"_editor_input\"[^>]+data\-value=\"(.*?)\"[^>]+\/>.+?<\/span>[^<>]*<\/div>/g;
// 替换前:
`<div class="_editor_label">
<input class="_editor_input" value="" data-value="abc" placeholder="请输入图注1-60个汉字" autocomplete="off"/>
<span class="_editor_stat">3/60</span>
</div>`
// 替换后:
// input 如果有值:
`<!-- del input start -->
<div class="_editor_label">
<span>图注</span>
</div>
<!-- del input end -->`
// input 没有值:
`<!-- del input empty -->`

/////////////////////////////////////////////////////////////////////////

// 编辑:视频图片占位图反显
var reg = /<video[^>]+src=\"([^>]+?)\"[^>]*><\/video>/g;
// 替换前:
`<video class="edui-upload-video video-js" controls="" preload="none" width="0" height="0" src="gg" data-setup="{}"></video>`
// 替换后:
`<img class="edui-upload-video" _url="gg" src="./src/images/convertVideo.jpg" alt="">`

/////////////////////////////////////////////////////////////////////////

// 编辑:图注反显
var reg = /<\!\-\- del input start \-\->.*?<span>([^<>]*?)<\/span>.*?<\!\-\- del input end \-\->|<\!\-\- del input empty \-\->/g;
// 替换前:
// input 如果有值:
`<!-- del input start -->
<div class="_editor_label">
<span>图注</span>
</div>
<!-- del input end -->`
// input 没有值:
`<!-- del input empty -->`
// 替换后:
`<div class="_editor_label">
<input class="_editor_input" value="" data-value="图注" placeholder="请输入图注1-60个汉字" autocomplete="off"/>
<span class="_editor_stat">3/60</span>
</div>`

/////////////////////////////////////////////////////////////////////////

// 编辑:关闭按钮反显
var reg = /<!--.*?(img|video).*?del btn -->/g
// 替换前:
// 图片
`<!-- img del btn -->`
// 视频
`<!-- video del btn -->`
// 替换后:
// 图片:
`<div data-del="img" class="_editor_title _editor_img_title">
<span data-del="img" class="_editor_close"></span>
</div>`
// 视频
`<div data-del="video" class="_editor_title _editor_video_title">
<span data-del="video" class="_editor_title_btn" data-ind="1551928713346">添加封面图</span>
<span data-del="video" class="_editor_close"></span>
</div>`


替换img标签里的style里为 width:100%;height:100% 其他属性不变

1
2
3
4
5
6
7
8
var str = `<img style="width:200px;line-height:40px" src="https://www.baidu.com" data-xxx="123"/>`

var _str = ''
var reg = /(<img.*style\=\").*?(\".*>)/gi
_str = str.replace(reg, '$1width:100%;height:100%$2')

console.log(_str)//<img style="width:100%;height:100%" src="https://www.baidu.com" data-xxx="123"/>

获取URL上的参数

1
2
3
4
5
6
7
8
9
10
11
12
13

var url = `https://aikahao.xcar.com.cn/video/151932.html` ;
var reg = /video\/(\d*).html/im
var r = url.match(reg)

console.log(r) //["video/151932.html", "151932", index: 28, input: "https://aikahao.xcar.com.cn/video/151932.html", groups: undefined]

var url2 = 'https://aikahao.xcar.com.cn/video?item=333333333.html';

var reg2 = /\?(?:id|item)=(\d*)/i //?:match时候不要这个分组
var r2 = url2.match(reg2);
console.log(r2) //["?item=333333333", "333333333", index: 33, input: "https://aikahao.xcar.com.cn/video?item=333333333.html", groups: undefined]

单列模式

单列模式 适合登录框这种场景或者需要全局通信的场景,不同页面调用其实都的是一个对象
这样不会生成多个实例造成混乱

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
{
//es6 单列模式
class Login{
constructor(state) {

}
login() {
console.log('....login')
}
}
//定义一个静态方法,这个方法是唯一的
Login.getInstance = (function(){
let instance;
return function() {
if(!instance) {
instance = new Login()
}
return instance
}
})()

//测试
let obj1 = new Login.getInstance();
obj1.login();

let obj2 = new Login.getInstance();
obj2.login();


console.log(obj1 === obj2) // ...login ...login true

let obj3 = new Login()
console.log(obj1 === obj3) //false 没有走静态方法,不可控


}




{
//ES5 单列
//对象字面量
var Login = {
status : false,
login:function() {
if(this.status == true) {
console.log('已经登陆了')
return
}
this.status = true;
console.log('....login')
},
logout:function() {
if(this.status == false) {
console.log('已经登出了')
return
}
this.status = false;
console.log('....logout')
}
}

var o1 = Login;
o1.login(); //login....

var o2 = Login
o2.login() //已经登录了
console.log(o1 === o2) //true
}



工厂模式

下面这个模拟 JQUERY 的其实就是个工厂模式 ,JQuery 这个大类提供 API
我们用的时候直接使用 window 上挂载的 $ 这个就相当于 JQuery 的工厂
不必关心工厂是怎么实现的

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
class jQuery {
constructor(selector) {
let slice = Array.prototype.slice
let dom = slice.call(document.querySelectorAll(selector))
let len = dom ? dom.length : 0
for (let i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
append(node) {

}
addClass(name) {

}
html(data) {

}
// 此处省略若干 API
}
window.$ = function (selector) {
return new jQuery(selector)
}
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
{
var carShop = function() {

};


carShop.prototype = {
sell: function(model) {
var car;
switch (model) {
case "1":
car = new Audi();
break;

case "2":
car = new Lowrider();
break;

case "3":
car = new Cruiser();
break;
default:


}

return car;

}

}
var ABC = new carShop();
var myCar = ABC.sell("1");

}

发布订阅者模式

观察者模式特点:
1,发布订阅
2,1 对多

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

{
//es6
//主题,保存状态,状态发生变化触发所有观察者对象
class Subject {
constructor() {
//要变化的状态
this.state = 0;
//观察者列表
this.observers = []
}
getState () {
return this.state
}
setState(state) {
this.state = state;
//状态变化通知所有观察者
this.notifyAllObservers()
}
notifyAllObservers() {
this.observers.forEach(observer => {
observer.update()
})
}
add(observer) {
//添加新的观察者
this.observers.push(observer)
}
}

//观察者
class Observer {
constructor(name,subject) {
this.name = name;
this.subject = subject;
//初始化观察者的时候把自身加入subject的观察者列表
this.subject.add(this)
}

update() {
console.log(`${this.name} update! state : ${this.subject.getState()}`)
}
}

//测试
let subject = new Subject() //生成一个新主题
let o1 = new Observer('weibin', subject)
let o2 = new Observer('xiaoming', subject)

subject.setState(0) //修改了主题 触发所有观察者
subject.setState(1)


}

{

//卖房的列子

<script>
let houseObj = {}; //房产中介 (发布者)

//房产中介的花名册登记谁要买房 (缓存列表)
//定义成数组 最后形式是[key1:fn,ke2:fn,ke3:fn]
//如果定义成对象,取得时候会有深拷贝的问题。
houseObj.list = [];

//增加订阅消息的顾客
houseObj.listen = function(key, fn) {
//如果没有key 一旦发布者发布一个消息,所有的订阅者都会收到消息,不论他们是否订阅了该消息
//如果标识在缓存列表里没有 则创建一个空数组占位
//[key1:fn,key2:fn,fn...]
//key1:fn 代表有标识 如果只有一个FN 无标识就return
if (!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(fn);
console.log(this.list);
//简写
// (this.list[key] || (this.list[key] = [])).push(fn);
};

//发布消息
houseObj.trigger = function() {
//取出消息名称key
let key = Array.prototype.shift.call(arguments);
//取出的FNS是一个数组
let fns = this.list[key];

if (!fns || fns.length === 0) {
return;
}

fns.forEach(v => {
v.apply(this, arguments);
});
};

houseObj.remove = function(key, fn) {
var fns = this.list[key];
if (!fns) {
return false;
}
if (!fn) {
fns && (fns.length = 0);
} else {
for (var i = fns.length - 1; i >= 0; i--) {
var _fn = fns[i];
if (_fn === fn) {
fns.splice(i, 1);
}
}
}
};

//订阅
houseObj.listen(
"a",
(fn1 = function(size) {
console.log(`小红要买${size}平米`);
})
);

houseObj.listen(
"b",
(fn3 = function(size) {
console.log(`小明要买${size}平米`);
})
);

houseObj.trigger("a", 100); //小红要买100平米
houseObj.trigger("b", 150); //小明要买150平米

houseObj.remove("a", fn1);
houseObj.trigger("a", 100); // 无打印输出
</script>

}



{
//jquery

var callbacks = $.Callbacks();

//添加观察者
callbacks.add(function(info) {
console.log('观察者1',info)
})

callbacks.add(function(info) {
console.log('观察者2',info)
})


callbacks.fire('gogogo') //观察者1 gogogo 观察者2 gogogo
}


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
<!DOCTYPE html>

<head>
<title></title>
<meta charset="UTF-8">
<style>


</style>
</head>

<body>


<div id="test">
<div style="width: 500px; height: 300px;">
<button id="btn1">改变城市1</button>
</div>
<div style="width: 500px; height: 300px;">
<button id="btn2">改变城市2</button>
</div>


</div>
<script>
// 功能:两个模块分别有两个城市切换按钮,切换其中一个,另一个模块得到通知跟着改变
;(function(win,doc){
function Subject() {
this.state = 0;
//观察者列表
this.observers = []
}
Subject.prototype = {
getState: function() {
return this.state
},
setState: function(state) {
this.state = state;
//状态变化通知所有观察者
this.notifyAllObservers()
},
notifyAllObservers:function() {
this.observers.forEach(observer => {
observer.update()
})
},
add:function(observer) {
//添加新的观察者
this.observers.push(observer)
}
}
function Observer(subject,fn,name) {
this.subject = subject;
this.fn = fn;
this.name = name;
this.subject.add(this)
}
Observer.prototype.update = function() {
this.fn[this.name](this.subject.getState())
}
var subject = new Subject()

window.subject = subject;
window.Observer = Observer;
})(window,document)

;(function(win,doc){
function A() {
this.btn = document.getElementById("btn1");
//默认城市ID是1
this.render(1);
this.click();
//注册自己为观察者,
//传入参数1:主题(写死subject就可以,已经挂在全局)
//传入参数2:自己
//传入参数3:自己的渲染方法名
new Observer(subject,this,'render')
}
A.prototype = {
click:function() {
var _this = this;
this.btn.addEventListener("click",function(){
//模拟接口
setTimeout(function(){
//主题变化,通知所有观察者重新渲染
subject.setState(2)
},500)
},false);
},
render:function(id) {
setTimeout(function(){
//渲染数据
console.log("A模块的ID是" + id)
},500)
}
}

new A()

})(window,document)

;(function(win,doc){
function B() {
this.btn = document.getElementById("btn2");
//默认城市ID是1
this.render(1);
this.click();
new Observer(subject,this,'render')
}
B.prototype = {
click:function() {
var _this = this;
this.btn.addEventListener("click",function(){
//模拟接口
setTimeout(function(){
subject.setState(3)
},500)
},false);
},
render:function(id) {
setTimeout(function(){
//渲染数据
console.log("B模块的ID是" + id)
},500)
}
}

new B()

})(window,document)





</script>
</body>

</html>

代理模式

代理模式 可以改变数据 –购物车https://github.com/weibsgz/design-cart/blob/master/src/demo/List/CreateItem.js

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


// 明星
let star = {
name : '魏彬',
age:25,
phone:'13401095555'
}
//代理
let agent = new Proxy(star,{
get : function(target,key) {
if(key === 'phone') {
//返回经纪人自己的手机号
return '13521497119'
}
if(key === 'price') {
//明星不报价
return 1000000
}

return target[key] //其余姓名,年龄可以返回明星的
},

set: function(target,key,val) {
if(key == 'customPrice') {
if (val < 1000000) {
throw new Error('价格太低了')
}
else {
target[key] = val;
return true;
}
}
}
})


console.log(agent.name, agent.age, agent.price, agent.phone) //魏彬 25 1000000 13521497119


agent.customPrice = 90;

console.log(agent.customPrice ) //价格太低了

策略模式

策略模式的优点:

  1. 策略模式可以有效避免很多 if 条件语句
  2. 策略模式符合开放-封闭原则,使代码更容易理解和扩展
  3. 策略模式中的代码可以复用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var sObj = {
"A":function(salary) {
return salary * 4;
},
"B":function(salary) {
return salary * 2
},
"C":function(salary) {
return salary
}
}

var cal = function(level,salary) {
return sObj[level] && sObj[level](salary)
}
console.log(cal('A',4000))

状态模式

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
//状态模式,避免大量IF-ELSE
//

function SuperMary() {
this.state = [];
this.fn = {
jump: function() {
console.log("jump");
},
move: function() {
console.log("move");
},
shoot: function() {
console.log("shoot");
}
};
}

SuperMary.prototype.changeState = function(action) {
//每次调用change 可以先清下数组,确保每次chageState都是新的状态
this.state = [];
if (typeof action === "string") {
this.state.push(action);
} else if (action instanceof Array) {
this.state = this.state.concat(action);
}
return this;
};

SuperMary.prototype.run = function() {
this.state.forEach(state => {
this.fn && this.fn[state]();
});
return this;
};

new SuperMary()
.changeState(["jump", "move"]) //jump move
.run()
.changeState("shoot") //shoot
.run();